<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>easyopen开发文档</title>
        <link rel="stylesheet" href="./static/highlight/styles/vs.css">
		<script src="./static/highlight/highlight.pack.js"></script>
		<script>hljs.initHighlightingOnLoad();</script>
        <link rel="stylesheet" type="text/css" href="./static/github2-rightpart.css" media="all">
        <link rel="stylesheet" type="text/css" href="./static/github1-contents.css">
        <link rel="stylesheet" href="./static/zTreeStyle.css" type="text/css">
        <style>
            .ztree li a.curSelectedNode {
                padding-top: 0px;
                background-color: #FFE6B0;
                color: black;
                height: 16px;
                border: 1px #FFB951 solid;
                opacity: 0.8;
            }
            .ztree{
                overflow: auto;
                height:100%;
                min-height: 200px;
                top: 0px;
            }
            .task-list{list-style-type: disc !important;}
            .container{margin: 0px !important;}
            #readme .markdown-body, #readme .plain{border:0px !important; font-size: 14px;}
        </style>
    </head>
    <body>
        <div>
            <div style="width:30%;">
                <ul id="tree" class="ztree" style="overflow: auto; position: fixed; z-index: 2147483647; border: 0px none; left: 0px; bottom: 0px;">
                <!-- 目录内容在网页另存为之后将插入到此处 -->
                </ul>
            </div>
            <div id="readme" style="width:70%;margin-left:25%;">
                <article class="markdown-body">


<!-- 请把你的html正文部分粘贴到此处，在浏览器中打开之后将会自动生成目录。如果想要将目录保留并嵌入到此文档中，只需在浏览器中“另存为->网页，全部”即可 -->
                    <div class="md-preview markdown-body" data-provider="markdown-preview" style="width: 100%; height: 48298px; resize: none;"><h1>easyopen介绍</h1>
                        <p>一个简单易用的接口开放平台，平台封装了常用的参数校验、结果返回等功能，开发者只需实现业务代码即可。</p>
                        <p>easyopen的功能类似于<a href="http://open.taobao.com/docs/api.htm?spm=a219a.7629065.0.0.6cQDnQ&amp;apiId=4">淘宝开放平台</a>，它的所有接口只提供一个url，通过参数来区分不同业务。这样做的好处是接口url管理方便了，平台管理者只需维护好接口参数即可。由于参数的数量是可知的，这样可以在很大程度上进行封装。封装完后平台开发者只需要写业务代码，其它功能可以通过配置来完成。</p>
                        <p>得益于Java的注解功能以及Spring容器对bean的管理，我们的开放接口平台就这样产生了。</p>
                        <h1>结构图</h1>
                        <p><img src="https://images.gitee.com/uploads/images/2018/0730/181724_32093df8_332975.png" alt="easyopen架构" title="easyopen架构.png"></p>
                        <ul>
                            <li>服务器启动完毕时，从Spring容器中找到被@ApiService标记的业务类</li>
                            <li>循环业务类，找到被@Api标记的方法，并保存对应的参数，method，对象信息。</li>
                            <li>客户端请求过来时，根据name-version可定位到具体的业务类中的某个方法，然后invoke方法。</li>
                            <li>包装结果返回。</li>
                        </ul>
                        <h1>功能特点</h1>
                        <ul>
                            <li>开箱即用，写完业务代码直接启动服务即可使用，无需其它配置。</li>
                            <li>参数自动校验，支持国际化参数校验（JSR-303）。</li>
                            <li>校验功能和结果返回功能实现各自独立，方便自定义实现或扩展。</li>
                            <li>采用注解来定义接口，维护简单方便。</li>
                            <li>支持i18n国际化消息返回。</li>
                            <li>自动生成文档页面，类似swagger。</li>
                            <li>采用数字签名进行参数验证，签名算法见：easyopen\签名算法.txt。</li>
                            <li>采用appKey-secret形式接入平台，即需要给接入方提供一个appKey和secret。</li>
                        </ul>
                        <h1>快速开始</h1>
                        <p>eclipse下（idea原理一样）</p>
                        <ul>
                            <li>下载或clone项目https://gitee.com/durcframework/easyopen.git <a href="https://gitee.com/durcframework/easyopen/repository/archive/master.zip">下载zip</a></li>
                            <li>eclipse右键import… -&gt; Exsiting Maven Projects。选择easyopen目录</li>
                            <li>导入到eclipse后会有三个工程，等待相关jar包下载。</li>
                            <li>全部jar下载完毕后，启动easyopen-server-manual项目，由于是spring-boot项目，直接运行ManualSpringbootApplication.java即可</li>
                            <li>在easyopen-sdk/sdk-java中找到SdkTest.java测试用例，运行单元测试。</li>
                        </ul>
                        <h2>编写业务类</h2>
                        <ul>
                            <li>新建一个java类名为HelloworldApi，并加上@ApiService注解</li>
                        </ul>
                        <p>加上@ApiService注解后这个类就具有了提供接口的能力。</p>
                        <pre><code class="hljs"><span class="hljs-annotation">@ApiService</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldApi</span> </span>{

}

</code></pre>
                        <ul>
                            <li>在类中添加一个方法</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-annotation">@Api</span>(name = <span class="hljs-string">"hello"</span>)
<span class="hljs-keyword">public</span> <span class="hljs-function">String <span class="hljs-title">helloworld</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">"hello world"</span>;
}
</code></pre>
                        <p>这个方法很简单，就返回了一个字符串，方法被@Api标记，表示对应的接口，name是接口名。</p>
                        <p>到此，一个业务方法就写完了，接下来是编写sdk并测试。</p>
                        <h2>编写SDK并测试(旧)</h2>
                        <p>此过程在easyopen-sdk中进行。</p>
                        <ul>
                            <li>新建Response响应类</li>
                        </ul>
                        <pre><code class="hljs">public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloResp</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">BaseResp&lt;String&gt;</span> {</span>

}
</code></pre>
                        <ul>
                            <li>新建Request请求类</li>
                        </ul>
                        <pre><code class="hljs">public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloReq</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">BaseNoParamReq&lt;HelloResp&gt;</span> {</span>

    public <span class="hljs-type">HelloReq</span>(<span class="hljs-type">String</span> name) {
        <span class="hljs-keyword">super</span>(name);
    }


}
</code></pre>
                        <p>BaseResp&lt;T&gt;的泛型参数指定返回体类型，这里指定String</p>
                        <ul>
                            <li>编写单元测试</li>
                        </ul>
                        <pre><code class="hljs">public class <span class="hljs-type">HelloTest</span> extends <span class="hljs-type">TestCase</span> {

    <span class="hljs-type">String</span> url = <span class="hljs-string">"http://localhost:8080/api"</span>;
    <span class="hljs-type">String</span> appKey = <span class="hljs-string">"test"</span>;
    <span class="hljs-type">String</span> secret = <span class="hljs-string">"123456"</span>;
    // 创建一个实例即可
    <span class="hljs-type">OpenClient</span> client = new <span class="hljs-type">OpenClient</span>(url, appKey, secret);

    @<span class="hljs-type">Test</span>
    public <span class="hljs-type">void</span> testGet() throws <span class="hljs-type">Exception</span> {
        <span class="hljs-type">HelloReq</span> req = new <span class="hljs-type">HelloReq</span>(<span class="hljs-string">"hello"</span>); // hello对应@<span class="hljs-type">Api</span>中的name属性，即接口名称

        <span class="hljs-type">HelloResp</span> <span class="hljs-literal">result</span> = client.request(req); // 发送请求
        <span class="hljs-keyword">if</span> (<span class="hljs-literal">result</span>.isSuccess()) {
            <span class="hljs-type">String</span> resp = <span class="hljs-literal">result</span>.getBody();
            <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(resp); // 返回hello world
        } <span class="hljs-keyword">else</span> {
            throw new <span class="hljs-type">RuntimeException</span>(<span class="hljs-literal">result</span>.getMsg());
        }

    }

}
</code></pre>
                        <p>这样，一个完整的接口就写完了。</p>
                        <h2>编写SDK并测试(新)</h2>
                        <h3>接口封装步骤</h3>
                        <p>比如获取商品信息接口</p>
                        <ul>
                            <li>接口名：goods.get</li>
                            <li>版本号：""(空字符串)</li>
                            <li>参数：goods_name 商品名称</li>
                            <li>返回信息</li>
                        </ul>
                        <pre><code class="hljs">{
    "<span class="hljs-attribute">code</span>":<span class="hljs-value"><span class="hljs-string">"0"</span></span>,
    "<span class="hljs-attribute">data</span>":<span class="hljs-value">{
        "<span class="hljs-attribute">goods_name</span>":<span class="hljs-value"><span class="hljs-string">"苹果iPhoneX"</span></span>,
        "<span class="hljs-attribute">id</span>":<span class="hljs-value"><span class="hljs-number">1</span></span>,
        "<span class="hljs-attribute">price</span>":<span class="hljs-value"><span class="hljs-number">8000</span>
    </span>}
</span>}
</code></pre>
                        <p>针对这个接口，封装步骤如下：</p>
                        <p>1.在<code>model</code>包下新建一个类来接收<code>data</code>部分</p>
                        <p>字段统一使用小写字母+下划线形式，如:name,user_age</p>
                        <pre><code class="hljs"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Goods</span> </span>{

    <span class="hljs-keyword">private</span> Long id;
    <span class="hljs-keyword">private</span> String goods_name;
    <span class="hljs-keyword">private</span> BigDecimal price;

    省略 <span class="hljs-keyword">get</span> <span class="hljs-keyword">set</span>
}
</code></pre>
                        <p>2.在<code>response</code>包下新建一个返回类，继承<code>BaseResponse</code></p>
                        <p>BaseResponse中有个泛型参数，填<code>Goods</code>类，表示返回的数据对象。</p>
                        <pre><code class="hljs">public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GetGoodsResponse</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">BaseResponse&lt;Goods&gt;</span> {</span>
}
</code></pre>
                        <p>3.在<code>request</code>包下新建一个请求类，继承<code>BaseRequest</code></p>
                        <p>BaseRequest中有个泛型参数，填<code>GetGoodsResponse</code>类，表示这个请求对应的返回类。
                            重写<code>name()</code>方法，填接口名。</p>
                        <p>如果要指定版本号，可重写<code>version()</code>方法，或者后续使用<code>request.setVersion(version)</code>进行设置</p>
                        <pre><code class="hljs">public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GetGoodsRequest</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">BaseRequest&lt;GetGoodsResponse&gt;</span> {</span>
    <span class="hljs-annotation">@Override</span>
    public <span class="hljs-type">String</span> name() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"goods.get"</span>;
    }
}
</code></pre>
                        <h3>使用方式</h3>
                        <pre><code class="hljs"><span class="hljs-comment">// 创建请求对象</span>
GetGoodsRequest request = <span class="hljs-keyword">new</span> GetGoodsRequest();
<span class="hljs-comment">// 请求参数</span>
GoodsParam param = <span class="hljs-keyword">new</span> GoodsParam();
param.setGoods_name(<span class="hljs-string">"iphone6"</span>);
request.setParam(param);

<span class="hljs-comment">// 发送请求</span>
GetGoodsResponse response = client.execute(request);

System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"--------------------"</span>);
<span class="hljs-keyword">if</span> (response.isSuccess()) {
    <span class="hljs-comment">// 返回结果</span>
    Goods goods = response.getData();
    System.<span class="hljs-keyword">out</span>.println(goods);
} <span class="hljs-keyword">else</span> {
    System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"errorMsg:"</span> + response.getMsg());
}
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"--------------------"</span>);
</code></pre>
                        <h3>使用方式2(懒人版)</h3>
                        <p>如果不想添加Request,Response,Model。可以用这种方式，返回data部分是Map&lt;String, Object&gt;，后续自己处理</p>
                        <pre><code class="hljs"><span class="hljs-comment">// 创建请求对象</span>
CommonRequest request = <span class="hljs-keyword">new</span> CommonRequest(<span class="hljs-string">"goods.get"</span>);
<span class="hljs-comment">// 请求参数</span>
Map&lt;<span class="hljs-keyword">String</span>, <span class="hljs-keyword">Object</span>&gt; param = <span class="hljs-keyword">new</span> <span class="hljs-keyword">HashMap</span>&lt;&gt;();
param.put(<span class="hljs-string">"goods_name"</span>, <span class="hljs-string">"iphone6"</span>);
request.setParam(param);

<span class="hljs-comment">// 发送请求</span>
CommonResponse response = client.execute(request);

System.out.<span class="hljs-built_in">println</span>(<span class="hljs-string">"--------------------"</span>);
<span class="hljs-keyword">if</span> (response.isSuccess()) {
    <span class="hljs-comment">// 返回结果</span>
    Map&lt;<span class="hljs-keyword">String</span>, <span class="hljs-keyword">Object</span>&gt; goods = response.getData();
    System.out.<span class="hljs-built_in">println</span>(goods.<span class="hljs-built_in">get</span>(<span class="hljs-string">"goods_name"</span>));
} <span class="hljs-keyword">else</span> {
    System.out.<span class="hljs-built_in">println</span>(<span class="hljs-string">"errorCode:"</span> + response.getCode() + <span class="hljs-string">",errorMsg:"</span> + response.getMsg());
}
System.out.<span class="hljs-built_in">println</span>(<span class="hljs-string">"--------------------"</span>);
</code></pre>
                        <h1>错误处理</h1>
                        <p>easyopen对错误处理已经封装好了，最简单的做法是向上throw即可，在最顶层的Controller会做统一处理。例如：</p>
                        <pre><code class="hljs"><span class="hljs-keyword">if</span>(StringUtils.isEmpty(param.getGoods_name())) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ApiException(<span class="hljs-string">"goods_name不能为null"</span>);
}
</code></pre>
                        <p>或者</p>
                        <pre><code class="hljs"><span class="hljs-keyword">if</span>(StringUtils.isEmpty(param.getGoods_name())) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"goods_name不能为null"</span>);
}
</code></pre>
                        <p>为了保证编码风格的一致性，推荐统一使用ApiException</p>
                        <h2>i18n国际化</h2>
                        <p>easyopen支持国际化消息。通过Request对象中的getLocale()来决定具体返回那种语言，客户端通过设置Accept-Language头部来决定返回哪种语言，中文是zh，英文是en。</p>
                        <p>easyopen通过模块化来管理国际化消息，这样做的好处结构清晰，维护方便。下面就来讲解如何设置国际化消息。</p>
                        <p>假设我们要对商品模块进行设置，步骤如下：</p>
                        <ul>
                            <li>在项目的resources下新建如下目录/i18n/isv</li>
                            <li>在isv目录下新建goods_error_zh_CN.properties属性文件</li>
                        </ul>
                        <p><img src="https://gitee.com/uploads/images/2018/0118/142511_feacd145_332975.png" alt="输入图片说明" title="2.png"></p>
                        <p>属性文件的文件名有规律， <strong>i18n/isv/goods_error</strong> 表示模块路径， <strong>_zh_CN.properties</strong> 表示中文错误消息。如果要使用英文错误，则新建一个goods_error_en.properties即可。</p>
                        <ul>
                            <li>在goods_error_zh_CN.properties中配置错误信息</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-special">#</span> 商品名字不能为空
isv.goods_error_100=<span class="hljs-command">\u</span>5546<span class="hljs-command">\u</span>54C1<span class="hljs-command">\u</span>540D<span class="hljs-command">\u</span>79F0<span class="hljs-command">\u</span>4E0D<span class="hljs-command">\u</span>80FD<span class="hljs-command">\u</span>4E3A<span class="hljs-command">\u</span>7A7A
</code></pre>
                        <p>isv.goods_error_为固定前缀，100为错误码，这两个值后续会用到。</p>
                        <p>接下来是把属性文件加载到国际化容器当中。</p>
                        <ul>
                            <li>添加国际化配置</li>
                        </ul>
                        <p>easyopen的所以配置操作都在ApiConfig类里面，配置工作可以在ApiController.initApiConfig(ApiConfig apiConfig)方法中进行，因此只要重写initApiConfig(ApiConfig apiConfig)方法就行了。</p>
                        <pre><code class="hljs"><span class="hljs-annotation">@Controller</span>
<span class="hljs-annotation">@RequestMapping</span>(value = <span class="hljs-string">"/api/v1"</span>)
public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexController</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">ApiController</span> {</span>

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">protected</span> void initApiConfig(<span class="hljs-type">ApiConfig</span> apiConfig) {
        <span class="hljs-comment">// 配置国际化消息</span>
        apiConfig.getIsvModules().add(<span class="hljs-string">"i18n/isv/goods_error"</span>);<span class="hljs-comment">// 模块路径</span>
        <span class="hljs-comment">// 配置appKey秘钥</span>
        <span class="hljs-type">Map</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">String</span>&gt; appSecretStore = <span class="hljs-keyword">new</span> <span class="hljs-type">HashMap</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">String</span>&gt;();
        appSecretStore.put(<span class="hljs-string">"test"</span>, <span class="hljs-string">"123456"</span>);

        apiConfig.addAppSecret(appSecretStore);
    }

}
</code></pre>
                        <p>添加一句apiConfig.getIsvModules().add(“i18n/isv/goods_error”);就行</p>
                        <ul>
                            <li>新建一个interface用来定义错误</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-comment">// 按模块来定义异常消息,团队开发可以分开进行</span>
<span class="hljs-keyword">public</span> <span class="hljs-interface"><span class="hljs-keyword">interface</span> GoodsErrors </span>{

    <span class="hljs-built_in">String</span> isvModule = <span class="hljs-string">"isv.goods_error_"</span>; <span class="hljs-comment">// 前缀</span>
    <span class="hljs-comment">// 100为前缀后面的错误码</span>
    <span class="hljs-comment">// 这句话即可找到isv.goods_error_100错误</span>
    ErrorMeta NO_GOODS_NAME = <span class="hljs-keyword">new</span> ErrorMeta(isvModule, <span class="hljs-string">"100"</span>, <span class="hljs-string">"商品名称不能为空."</span>);
}
</code></pre>
                        <p>接下来就可以使用了</p>
                        <pre><code class="hljs"><span class="hljs-keyword">if</span> (<span class="hljs-type">StringUtils</span>.isEmpty(param.getGoods_name<span class="hljs-literal">()</span>)) {
    throw <span class="hljs-type">GoodsErrors</span>.<span class="hljs-type">NO_GOODS_NAME</span>.getException<span class="hljs-literal">()</span>;
}
</code></pre>
                        <h3>国际化消息传参</h3>
                        <p>即代码中变量传入到properties文件中去，做法是采用{0},{1}占位符。0代表第一个参数，1表示第二个参数。</p>
                        <pre><code class="hljs"><span class="hljs-special">#</span> 商品名称太短，不能小于<span class="hljs-special">{</span>0<span class="hljs-special">}</span>个字
isv.goods_error_101=<span class="hljs-command">\u</span>5546<span class="hljs-command">\u</span>54C1<span class="hljs-command">\u</span>540D<span class="hljs-command">\u</span>79F0<span class="hljs-command">\u</span>592A<span class="hljs-command">\u</span>77ED<span class="hljs-command">\uFF</span>0C<span class="hljs-command">\u</span>4E0D<span class="hljs-command">\u</span>80FD<span class="hljs-command">\u</span>5C0F<span class="hljs-command">\u</span>4E8E<span class="hljs-special">{</span>0<span class="hljs-special">}</span><span class="hljs-command">\u</span>4E2A<span class="hljs-command">\u</span>5B57
</code></pre>
                        <pre><code class="hljs"><span class="hljs-keyword">if</span>(<span class="hljs-built_in">param</span>.getGoods_name().<span class="hljs-built_in">length</span>() &lt; <span class="hljs-number">3</span>) {
    throw GoodsErrors.SHORT_GOODS_NAME.getException(<span class="hljs-number">3</span>);<span class="hljs-comment"> // 这里的“3”会填充到{0}中</span>
}
</code></pre>
                        <p>直接放进getException(Object… params)方法参数中，因为是可变参数，可随意放。</p>
                        <h1>业务参数校验</h1>
                        <p>业务参数校验采用JSR-303方式，关于JSR-303介绍可以参考这篇博文：<a href="https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/">JSR 303 - Bean Validation 介绍及最佳实践</a></p>
                        <p>在参数中使用注解即可，框架会自动进行验证。如下面一个添加商品接口，它的参数是GoodsParam</p>
                        <pre><code class="hljs">@Api(name = <span class="hljs-string">"goods.add"</span>)
public void addGoods(GoodsParam param) {
    <span class="hljs-keyword">...</span>
}
</code></pre>
                        <p>在GoodsParam中添加JSR-303注解:</p>
                        <pre><code class="hljs"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GoodsParam</span> {</span>
    <span class="hljs-annotation">@NotEmpty</span>(message = <span class="hljs-string">"商品名称不能为空"</span>)
    <span class="hljs-keyword">private</span> String goods_name;
    <span class="hljs-comment">// 省略get，set</span>
}
</code></pre>
                        <p>如果不传商品名称则返回</p>
                        <pre><code class="hljs">{"<span class="hljs-attribute">code</span>":<span class="hljs-value"><span class="hljs-string">"100"</span></span>,"<span class="hljs-attribute">msg</span>":<span class="hljs-value"><span class="hljs-string">"商品名称不能为空"</span></span>}
</code></pre>
                        <h2>参数校验国际化</h2>
                        <p>国际化的配置方式如下：</p>
                        <pre><code class="hljs"><span class="hljs-annotation">@NotEmpty</span>(message = <span class="hljs-string">"{goods.name.notNull}"</span>)
<span class="hljs-keyword">private</span> String goods_name;
</code></pre>
                        <p>国际化资源文件goods_error_en.properties中添加：</p>
                        <pre><code class="hljs">goods<span class="hljs-class">.name</span><span class="hljs-class">.notNull</span>=The goods_name can not be null
</code></pre>
                        <p>goods_error_zh_CN.properties中添加：</p>
                        <pre><code class="hljs">goods.name.notNull=<span class="hljs-command">\u</span>5546<span class="hljs-command">\u</span>54C1<span class="hljs-command">\u</span>540D<span class="hljs-command">\u</span>79F0<span class="hljs-command">\u</span>4E0D<span class="hljs-command">\u</span>80FD<span class="hljs-command">\u</span>4E3Anull
</code></pre>
                        <h2>参数校验国际化传参</h2>
                        <p>下面校验商品名称的长度，要求大于等于3且小于等于20。数字3和20要填充到国际化资源中去。</p>
                        <pre><code class="hljs"><span class="hljs-comment">// 传参的格式：{xxx}=value1,value2...</span>
@Length(<span class="hljs-keyword">min</span> <span class="hljs-subst">=</span> <span class="hljs-number">3</span>, <span class="hljs-keyword">max</span> <span class="hljs-subst">=</span> <span class="hljs-number">20</span>, message <span class="hljs-subst">=</span> <span class="hljs-string">"{goods.name.length}=3,20"</span>)
<span class="hljs-keyword">private</span> <span class="hljs-built_in">String</span> goods_name;
</code></pre>
                        <p>goods_error_en.properties:</p>
                        <pre><code class="hljs">goods.<span class="hljs-property">name</span>.<span class="hljs-property">length</span>=The goods_name <span class="hljs-property">length</span> must &gt;= {<span class="hljs-number">0</span>} <span class="hljs-keyword">and</span> &lt;= {<span class="hljs-number">1</span>}
</code></pre>
                        <p>goods_error_zh_CN.properties中添加：</p>
                        <pre><code class="hljs">goods.name.length=<span class="hljs-command">\u</span>5546<span class="hljs-command">\u</span>54C1<span class="hljs-command">\u</span>540D<span class="hljs-command">\u</span>79F0<span class="hljs-command">\u</span>957F<span class="hljs-command">\u</span>5EA6<span class="hljs-command">\u</span>5FC5<span class="hljs-command">\u</span>987B<span class="hljs-command">\u</span>5927<span class="hljs-command">\u</span>4E8E<span class="hljs-command">\u</span>7B49<span class="hljs-command">\u</span>4E8E<span class="hljs-special">{</span>0<span class="hljs-special">}</span><span class="hljs-command">\u</span>4E14<span class="hljs-command">\u</span>5C0F<span class="hljs-command">\u</span>4E8E<span class="hljs-command">\u</span>7B49<span class="hljs-command">\u</span>4E8E<span class="hljs-special">{</span>1<span class="hljs-special">}</span>

</code></pre>
                        <p>这样value1，value2会分别填充到{0},{1}中</p>
                        <h1>自定义结果返回</h1>
                        <h2>改变json输出策略</h2>
                        <p>默认使用阿里的fastjson进行json输出</p>
                        <pre><code class="hljs">JSON.<span class="hljs-function"><span class="hljs-title">toJSONString</span><span class="hljs-params">(obj)</span></span>
</code></pre>
                        <p>如果要更换输出策略，操作方式如下：</p>
                        <pre><code class="hljs">@Override
protected void initApiConfig(ApiConfig apiConfig) {
    <span class="hljs-keyword">...</span>
    // 自定义json格式输出,将null字符串变成<span class="hljs-string">""</span>
    apiConfig.setJsonResultSerializer(new JsonResultSerializer(){
        @Override
        public String serialize(Object obj) {
            <span class="hljs-keyword">return</span> JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue,SerializerFeature.WriteNullStringAsEmpty);
        }
    });
    <span class="hljs-keyword">...</span>
}
</code></pre>
                        <p>上面示例是将null字符串变成""，如果是给安卓app提供接口，这个会很管用。</p>
                        <h2>改变json格式</h2>
                        <p>easyopen默认的返回类是ApiResult，解析成json格式为：</p>
                        <pre><code class="hljs">{
	<span class="hljs-string">"code"</span>: <span class="hljs-string">"0"</span>,
	<span class="hljs-string">"msg"</span>: <span class="hljs-string">""</span>,
	<span class="hljs-string">"data"</span>: <span class="hljs-decorator">{...}</span>
}
</code></pre>
                        <p>我们也可以自定义返回结果，比如我们要返回这样的json：</p>
                        <pre><code class="hljs">{
    <span class="hljs-string">"errCode"</span>:<span class="hljs-string">"0"</span>,
    <span class="hljs-string">"errMsg"</span>:<span class="hljs-string">""</span>,
    <span class="hljs-string">"body"</span>:<span class="hljs-decorator">{...}</span>
}
</code></pre>
                        <ul>
                            <li>首先，新建结果类，实现com.gitee.easyopen.Result接口：</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-keyword">import</span> com.gitee.easyopen.Result;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyResult</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Result</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> serialVersionUID = -<span class="hljs-number">6618981510574135069L</span>;

    <span class="hljs-keyword">private</span> String errCode;
    <span class="hljs-keyword">private</span> String errMsg;
    <span class="hljs-keyword">private</span> String body;

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setCode</span><span class="hljs-params">(Object code)</span> </span>{
        <span class="hljs-keyword">this</span>.setErrCode(String.valueOf(code));
    }

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setMsg</span><span class="hljs-params">(String msg)</span> </span>{
        <span class="hljs-keyword">this</span>.setErrMsg(msg);
    }

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setData</span><span class="hljs-params">(Object data)</span> </span>{
        <span class="hljs-keyword">this</span>.setBody(String.valueOf(data));
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getErrCode</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> errCode;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setErrCode</span><span class="hljs-params">(String errCode)</span> </span>{
        <span class="hljs-keyword">this</span>.errCode = errCode;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getErrMsg</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> errMsg;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setErrMsg</span><span class="hljs-params">(String errMsg)</span> </span>{
        <span class="hljs-keyword">this</span>.errMsg = errMsg;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getBody</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> body;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setBody</span><span class="hljs-params">(String body)</span> </span>{
        <span class="hljs-keyword">this</span>.body = body;
    }
}
</code></pre>
                        <p>MyResult类可定义自己想要的字段名字，实现Result接口对应的方法即可</p>
                        <ul>
                            <li>然后，新建一个结果生成器，实现ResultCreator接口：</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-keyword">import</span> com.gitee.easyopen.Result;
<span class="hljs-keyword">import</span> com.gitee.easyopen.ResultCreator;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyResultCreator</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ResultCreator</span> </span>{

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">createResult</span><span class="hljs-params">(Object returnObj)</span> </span>{
        MyResult ret = <span class="hljs-keyword">new</span> MyResult();
        ret.setCode(<span class="hljs-number">0</span>);
        ret.setData(returnObj);
        <span class="hljs-keyword">return</span> ret;
    }

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">createErrorResult</span><span class="hljs-params">(Object code, String errorMsg, Object data)</span> </span>{
        MyResult ret = <span class="hljs-keyword">new</span> MyResult();
        ret.setCode(code);
        ret.setMsg(errorMsg);
        ret.setData(data);
        <span class="hljs-keyword">return</span> ret;
    }

}
</code></pre>
                        <p>ResultCreator接口定义两个方法，createResult是返回正确内容的方法，createErrorResult是返回错误时候的方法。
                            分别实现它们，用我们刚才新建的MyResult类。</p>
                        <ul>
                            <li>最后，配置结果生成器，在initApiConfig方法中配置：</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-annotation">@Controller</span>
<span class="hljs-annotation">@RequestMapping</span>(<span class="hljs-string">"/project/api"</span>)
public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexController</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">ApiController</span> {</span>

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">protected</span> void initApiConfig(<span class="hljs-type">ApiConfig</span> apiConfig) {

        <span class="hljs-comment">// 配置结果生成器</span>
        apiConfig.setResultCreator(<span class="hljs-keyword">new</span> <span class="hljs-type">MyResultCreator</span>());
        省略其它代码...
    }

}
</code></pre>
                        <p>调用apiConfig.setResultCreator(new MyResultCreator());进行配置</p>
                        <h1>自定义序列化</h1>
                        <p>easyopen序列化使用fastjson处理json，xstream处理xml。现在我们来自定义实现一个json处理：</p>
                        <ul>
                            <li>新建一个类JsonFormatter，实现ResultSerializer接口</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JsonFormatter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ResultSerializer</span> </span>{
    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function">String <span class="hljs-title">serialize</span><span class="hljs-params">(Object obj)</span> </span>{
        Gson gson = <span class="hljs-keyword">new</span> Gson();
        <span class="hljs-function"><span class="hljs-keyword">return</span> gson.<span class="hljs-title">toJson</span><span class="hljs-params">(obj)</span></span>;
    }
}

</code></pre>
                        <p>这里使用了Gson。</p>
                        <ul>
                            <li>在apiConfig中配置</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-annotation">@Controller</span>
<span class="hljs-annotation">@RequestMapping</span>(<span class="hljs-string">"/project/api"</span>)
public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexController</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">ApiController</span> {</span>

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">protected</span> void initApiConfig(<span class="hljs-type">ApiConfig</span> apiConfig) {
        <span class="hljs-comment">// 自定义json序列化</span>
        apiConfig.setJsonResultSerializer(<span class="hljs-keyword">new</span> <span class="hljs-type">JsonFormatter</span>());
        省略其它代码...
    }

}
</code></pre>
                        <h1>定义接口版本号</h1>
                        <p>设置@Api注解的version属性，不指定默认为""。@Api(name = “goods.get” , version = “2.0”)</p>
                        <h1>在业务类中获取Request对象</h1>
                        <pre><code class="hljs"><span class="hljs-attribute">HttpServletRequest request </span>=<span class="hljs-string"> ApiContext.getRequest();
</span></code></pre>
                        <h1>接口交互详解</h1>
                        <h2>请求参数</h2>
                        <p>easyopen定义了7个固定的参数，用json接收</p>
                        <pre><code class="hljs">{
	"<span class="hljs-attribute">name</span>":<span class="hljs-value"><span class="hljs-string">"goods.get"</span></span>,
	"<span class="hljs-attribute">version</span>":<span class="hljs-value"><span class="hljs-string">"2.0"</span></span>,
	"<span class="hljs-attribute">app_key</span>":<span class="hljs-value"><span class="hljs-string">"test"</span></span>,
	"<span class="hljs-attribute">data</span>":<span class="hljs-value"><span class="hljs-string">"%7B%22goods_name%22%3A%22iphone6%22%7D"</span></span>,
	"<span class="hljs-attribute">format</span>":<span class="hljs-value"><span class="hljs-string">"json"</span></span>,
	"<span class="hljs-attribute">timestamp</span>":<span class="hljs-value"><span class="hljs-string">"2018-01-16 17:02:02"</span></span>,
	"<span class="hljs-attribute">sign</span>":<span class="hljs-value"><span class="hljs-string">"4CB446DF67DB3637500D4715298CE4A3"</span>
</span>}
</code></pre>
                        <ul>
                            <li>name:接口名称</li>
                            <li>version：接口版本号</li>
                            <li>app_key：分配给客户端的app_key</li>
                            <li>data：业务参数，json格式并且urlencode</li>
                            <li>format：返回格式，json,xml两种</li>
                            <li>timestamp：时间戳，yyyy-MM-dd HH:mm:ss</li>
                            <li>sign：签名串</li>
                        </ul>
                        <p>其中sign需要使用双方约定的签名算法来生成。</p>
                        <h2>签名算法</h2>
                        <p>签名算法描述如下：</p>
                        <ol>
                            <li>将请求参数按参数名升序排序；</li>
                            <li>按请求参数名及参数值相互连接组成一个字符串：<code>&lt;paramName1&gt;&lt;paramValue1&gt;&lt;paramName2&gt;&lt;paramValue2&gt;...</code>；</li>
                            <li>将应用密钥分别添加到以上请求参数串的头部和尾部：<code>&lt;secret&gt;&lt;请求参数字符串&gt;&lt;secret&gt;</code>；</li>
                            <li>对该字符串进行MD5运算，得到一个二进制数组；</li>
                            <li>将该二进制数组转换为十六进制的字符串(全部大写)，该字符串即是这些请求参数对应的签名；</li>
                            <li>该签名值使用<code>sign</code>参数一起和其它请求参数一起发送给服务开放平台。</li>
                        </ol>
                        <p>伪代码:</p>
                        <pre><code class="hljs"><span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>,<span class="hljs-built_in">Object</span>&gt; paramsMap = <span class="hljs-keyword">new</span> ...; <span class="hljs-comment">// 参数</span>

<span class="hljs-built_in">Set</span>&lt;<span class="hljs-built_in">String</span>&gt; keySet = paramsMap.keySet();
<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt; paramNames = <span class="hljs-keyword">new</span> ArrayList&lt;<span class="hljs-built_in">String</span>&gt;(keySet);
<span class="hljs-comment">// 1. </span>
Collections.sort(paramNames);

StringBuilder paramNameValue = <span class="hljs-keyword">new</span> StringBuilder();
<span class="hljs-comment">// 2. </span>
<span class="hljs-keyword">for</span> (<span class="hljs-built_in">String</span> paramName : paramNames) {
    paramNameValue.append(paramName).append(paramsMap.<span class="hljs-literal">get</span>(paramName));
}
<span class="hljs-comment">// 3.</span>
<span class="hljs-built_in">String</span> source = secret + paramNameValue.toString() + secret;
<span class="hljs-comment">// 4.&amp; 5.</span>
<span class="hljs-built_in">String</span> sign = md5(source).toUpperCase();
<span class="hljs-comment">// 6. </span>
paramsMap.put(<span class="hljs-string">"sign"</span>,sign);

</code></pre>
                        <h2>请求方式</h2>
                        <p>请求数据放在body体中，采用json格式。这里给出一个POST工具类：</p>
                        <pre><code class="hljs">public class <span class="hljs-type">PostUtil</span> {

    private <span class="hljs-keyword">static</span> final <span class="hljs-type">String</span> <span class="hljs-type">UTF8</span> = <span class="hljs-string">"UTF-8"</span>;
    private <span class="hljs-keyword">static</span> final <span class="hljs-type">String</span> <span class="hljs-type">CONTENT_TYPE_JSON</span> = <span class="hljs-string">"application/json"</span>;
    private <span class="hljs-keyword">static</span> final <span class="hljs-type">String</span> <span class="hljs-type">ACCEPT_LANGUAGE</span> = <span class="hljs-string">"Accept-Language"</span>;


    /**
     * <span class="hljs-type">POST</span>请求
     * @param url
     * @param params
     * @param lang 语言zh,en
     * @<span class="hljs-keyword">return</span>
     * @throws <span class="hljs-type">Exception</span>
     */
    public <span class="hljs-keyword">static</span> <span class="hljs-type">String</span> post(<span class="hljs-type">String</span> url, <span class="hljs-type">JSONObject</span> params, <span class="hljs-type">String</span> lang) throws <span class="hljs-type">Exception</span> {
        <span class="hljs-type">String</span> encode = <span class="hljs-type">UTF8</span>;
        // 使用 <span class="hljs-type">POST</span> 方式提交数据
        <span class="hljs-type">PostMethod</span> <span class="hljs-keyword">method</span> = new <span class="hljs-type">PostMethod</span>(url);
        <span class="hljs-keyword">try</span> {
            <span class="hljs-type">String</span> requestBody = params.toJSONString();
            // 请求数据放在body体中，采用json格式
            <span class="hljs-keyword">method</span>.setRequestEntity(new <span class="hljs-type">StringRequestEntity</span>(requestBody, <span class="hljs-type">CONTENT_TYPE_JSON</span>, encode));
            // 设置请求语言
            <span class="hljs-keyword">method</span>.setRequestHeader(<span class="hljs-type">ACCEPT_LANGUAGE</span>, lang);

            <span class="hljs-type">HttpClient</span> client = new <span class="hljs-type">HttpClient</span>();
            <span class="hljs-type">int</span> state = client.executeMethod(<span class="hljs-keyword">method</span>); // 返回的状态

            <span class="hljs-keyword">if</span> (state != <span class="hljs-type">HttpStatus</span>.<span class="hljs-type">SC_OK</span>) {
                throw new <span class="hljs-type">Exception</span>(<span class="hljs-string">"HttpStatus is "</span> + state);
            }

            <span class="hljs-type">String</span> response = <span class="hljs-keyword">method</span>.getResponseBodyAsString();

            <span class="hljs-keyword">return</span> response; // response就是最后得到的结果
        } catch (<span class="hljs-type">Exception</span> e) {
            throw e;
        } <span class="hljs-keyword">finally</span> {
            <span class="hljs-keyword">method</span>.releaseConnection();
        }
    }

}
</code></pre>
                        <ul>
                            <li>请求操作代码：</li>
                        </ul>
                        <pre><code class="hljs">@Test
<span class="hljs-keyword">public</span> <span class="hljs-literal">void</span> testGet() throws Exception {

    <span class="hljs-built_in">Map</span><span class="hljs-subst">&lt;</span><span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span><span class="hljs-subst">&gt;</span> param <span class="hljs-subst">=</span> <span class="hljs-literal">new</span> HashMap<span class="hljs-subst">&lt;</span><span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span><span class="hljs-subst">&gt;</span>();

    Goods goods <span class="hljs-subst">=</span> <span class="hljs-literal">new</span> Goods();
    <span class="hljs-built_in">String</span> <span class="hljs-built_in">data</span> <span class="hljs-subst">=</span> JSON<span class="hljs-built_in">.</span>toJSONString(goods);
    <span class="hljs-built_in">data</span> <span class="hljs-subst">=</span> URLEncoder<span class="hljs-built_in">.</span>encode(<span class="hljs-built_in">data</span>, <span class="hljs-string">"UTF-8"</span>);

    param<span class="hljs-built_in">.</span>put(<span class="hljs-string">"name"</span>, <span class="hljs-string">"hello"</span>);
    param<span class="hljs-built_in">.</span>put(<span class="hljs-string">"app_key"</span>, appId);
    param<span class="hljs-built_in">.</span>put(<span class="hljs-string">"data"</span>, <span class="hljs-built_in">data</span>);
    param<span class="hljs-built_in">.</span>put(<span class="hljs-string">"timestamp"</span>, getTime());
    param<span class="hljs-built_in">.</span>put(<span class="hljs-string">"version"</span>, <span class="hljs-string">""</span>);
    param<span class="hljs-built_in">.</span>put(<span class="hljs-string">"format"</span>, <span class="hljs-string">"json"</span>);

    <span class="hljs-built_in">String</span> sign <span class="hljs-subst">=</span> ApiUtil<span class="hljs-built_in">.</span>buildSign(param, secret);

    param<span class="hljs-built_in">.</span>put(<span class="hljs-string">"sign"</span>, sign);

    System<span class="hljs-built_in">.</span>out<span class="hljs-built_in">.</span>println(<span class="hljs-string">"请求内容:"</span> <span class="hljs-subst">+</span> JSON<span class="hljs-built_in">.</span>toJSONString(param));

    <span class="hljs-built_in">String</span> resp <span class="hljs-subst">=</span> PostUtil<span class="hljs-built_in">.</span>post(url, param,<span class="hljs-string">"zh"</span>);

    System<span class="hljs-built_in">.</span>out<span class="hljs-built_in">.</span>println(resp);
}

<span class="hljs-keyword">public</span> <span class="hljs-built_in">String</span> getTime() {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">new</span> SimpleDateFormat(<span class="hljs-string">"yyyy-MM-dd HH:mm:ss"</span>)<span class="hljs-built_in">.</span>format(<span class="hljs-literal">new</span> <span class="hljs-built_in">Date</span>());
}
</code></pre>
                        <h2>服务端验证</h2>
                        <p>服务端拿到请求数据后会sign字段进行验证，验证步骤如下：</p>
                        <ol>
                            <li>根据客户端传过来的app_key拿到服务端保存的secret</li>
                            <li>拿到secret后通过签名算法生成服务端的serverSign</li>
                            <li>比较客户端sign和serverSign是否相等，如果相等则证明客户端传来的数据是合法数据，否则不通过返回错误信息。</li>
                            <li>处理业务，返回结果</li>
                        </ol>
                        <h1>忽略验证</h1>
                        <p>如果某个接口不需要进行验证工作，可以在@Api注解上设置属性ignoreValidate=true（默认false）。这样调用接口时，不会进行验证操作。</p>
                        <p>同样的，在@ApiService注解里也有一个对应的ignoreValidate属性，设置为true的话，Service类下面所有的接口都忽略验证。</p>
                        <h2>忽略所有接口验证</h2>
                        <p>设置ApiConfig.setIgnoreValidate(true)，所有接口的签名认证操作都将忽略（业务参数校验除外）。</p>
                        <h1>生成文档页面</h1>
                        <p>easyopen提供一个简单的api文档查看页面，类似于swagger，基于注解功能来生成文档页面。生成的文档页面可以查看参数、结果说明，也可以进行模拟请求。对于前后端分离的项目来说会很有帮助。文档界面如下图所示：</p>
                        <p><img src="https://gitee.com/uploads/images/2018/0203/145842_55f2794e_332975.png" alt="输入图片说明" title="3.png"></p>
                        <p>左边的树形菜单对应文档名称，点击树可前往查看对应的接口说明。点击请求按钮可以发起请求进行测试。可修改业务参数中的值进行测试。</p>
                        <p>下面来讲解文档注解的使用方法。</p>
                        <p>文档页面默认是关闭的，需要在ApiConfig中设置</p>
                        <pre><code class="hljs">apiConfig.setShowDoc(<span class="hljs-keyword">true</span>);<span class="hljs-regexp">//</span> 为<span class="hljs-keyword">true</span>开启文档页面。
</code></pre>
                        <p>接下来对获取商品接口设置文档信息：</p>
                        <pre><code class="hljs">@Api(name = <span class="hljs-string">"goods.get"</span>)
public Goods getGoods(GoodsParam param) {
    <span class="hljs-keyword">...</span>
    <span class="hljs-keyword">return</span> goods;
}
</code></pre>
                        <h2>设置文档注解</h2>
                        <p>在接口方法上添加一个@ApiDocMethod注解。</p>
                        <pre><code class="hljs"><span class="hljs-variable">@Api</span>(name = <span class="hljs-string">"goods.get"</span>)
<span class="hljs-variable">@ApiDocMethod</span>(description=<span class="hljs-string">"获取商品"</span>) <span class="hljs-comment">// 文档注解，description为接口描述</span>
public Goods <span class="hljs-function">getGoods</span>(GoodsParam param) {
    ...
    <span class="hljs-tag">return</span> <span class="hljs-tag">goods</span>;
}
</code></pre>
                        <h2>设置参数注解</h2>
                        <ul>
                            <li>方式1：直接在<code>@ApiDocMethod</code>注解中申明<code>params</code>属性</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-variable">@Api</span>(name = <span class="hljs-string">"goods.get"</span>)
<span class="hljs-variable">@ApiDocMethod</span>(description = <span class="hljs-string">"获取商品"</span>, params = {
            <span class="hljs-variable">@ApiDocField</span>(name = <span class="hljs-string">"id"</span>, dataType = DataType.INT, example = <span class="hljs-string">"1"</span>),
            <span class="hljs-variable">@ApiDocField</span>(name = <span class="hljs-string">"goodsName"</span>, dataType = DataType.STRING, description = <span class="hljs-string">"商品名称"</span>, example = <span class="hljs-string">"iPhone6"</span>),
    })
public Goods <span class="hljs-function">getGoods</span>(GoodsParam param) {
    ...
    <span class="hljs-tag">return</span> <span class="hljs-tag">goods</span>;
}
</code></pre>
                        <ul>
                            <li>方式2：在实体类的字段上申明<code>@ApiDocField</code>注解</li>
                        </ul>
                        <p>进入GoodsParam类，使用@ApiDocField注解</p>
                        <pre><code class="hljs"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GoodsParam</span> {</span>

    <span class="hljs-annotation">@ApiDocField</span>(description = <span class="hljs-string">"商品名称"</span>, required = <span class="hljs-literal">true</span>, example = <span class="hljs-string">"iphoneX"</span>)
    <span class="hljs-keyword">private</span> String goods_name;
    <span class="hljs-comment">// 省略 get set</span>
}
</code></pre>
                        <p>@ApiDocField注解用来定义字段信息，@ApiDocField注解定义如下</p>
                        <pre><code class="hljs">/<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 字段描述
     <span class="hljs-keyword">*</span>/
    String description() default <span class="hljs-string">""</span>;

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 字段名
     <span class="hljs-keyword">*</span>/
    String name() default <span class="hljs-string">""</span>;

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 数据类型，int string float
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@return</span>
     <span class="hljs-keyword">*</span>/
    DataType dataType() default DataType.STRING;

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 是否必填
     <span class="hljs-keyword">*</span>/
    boolean required() default false;
</code></pre>
                        <p><strong>注：方式1，方式2同时存在的情况下优先使用方式1</strong></p>
                        <h2>设置返回结果注解</h2>
                        <p>同样，在Goods类中设置@ApiDocField</p>
                        <pre><code class="hljs"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> Goods {
    @ApiDocField(<span class="hljs-keyword">description</span> = <span class="hljs-string">"id"</span>)
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">Long</span> id;
    @ApiDocField(<span class="hljs-keyword">description</span> = <span class="hljs-string">"商品名称"</span>)
    <span class="hljs-keyword">private</span> String goods_name;
    @ApiDocField(<span class="hljs-keyword">description</span> = <span class="hljs-string">"价格"</span>, dataType = DataType.<span class="hljs-keyword">FLOAT</span>)
    <span class="hljs-keyword">private</span> BigDecimal price;
    <span class="hljs-comment">// 省略 get set</span>
}
</code></pre>
                        <p>到此已经设置完毕了，可以访问url进行预览</p>
                        <h2>文档页面URL</h2>
                        <p>文档页面的url格式为：apiUrl + “/doc”。如：apiUrl为http://localhost:8080/api/v1，那么文档页面就是：<a href="http://localhost:8080/api/v1/doc">http://localhost:8080/api/v1/doc</a></p>
                        <h2>List返回</h2>
                        <p>如果接口方法返回一个List&lt;T&gt;，设置方式如下</p>
                        <pre><code class="hljs">    <span class="hljs-variable">@Api</span>(name = <span class="hljs-string">"goods.list"</span>, version = <span class="hljs-string">"2.0"</span>)
    <span class="hljs-variable">@ApiDocMethod</span>(description=<span class="hljs-string">"获取商品列表"</span>
        ,results={<span class="hljs-variable">@ApiDocField</span>(description=<span class="hljs-string">"商品列表"</span>,name=<span class="hljs-string">"list"</span>, elementClass=Goods.class)}
    )
    public List&lt;Goods&gt; <span class="hljs-function">listGoods</span>(GoodsParam param) {

    }
</code></pre>
                        <p>elementClass对应List中的元素类型</p>
                        <h2>设置文档密码（v1.8.5）</h2>
                        <pre><code class="hljs">apiConfig.setDocPassword(<span class="hljs-string">"doc123"</span>); <span class="hljs-comment">// 设置文档页面密码</span>
</code></pre>
                        <p>设置完成后，需要输入密码才能访问文档页面，可配合线上联调，排错。</p>
                        <h2>修改文档页返回字段名（v1.8.5）</h2>
                        <p>文档页面默认返回字段名为code,msg,data。可在IndexController中重写<code>processDocVelocityContext()</code>定义</p>
                        <pre><code class="hljs">@Override
    protected void processDocVelocityContext(VelocityContext context) {
        context.<span class="hljs-keyword">put</span>(<span class="hljs-string">"code_name"</span>, <span class="hljs-string">"errCode"</span>);
        context.<span class="hljs-keyword">put</span>(<span class="hljs-string">"code_description"</span>, <span class="hljs-string">"状态值，\"0\"表示成功，其它都是失败"</span>);

        context.<span class="hljs-keyword">put</span>(<span class="hljs-string">"msg_name"</span>, <span class="hljs-string">"errMsg"</span>);
        context.<span class="hljs-keyword">put</span>(<span class="hljs-string">"msg_description"</span>, <span class="hljs-string">"错误信息，出错时显示"</span>);

        context.<span class="hljs-keyword">put</span>(<span class="hljs-string">"data_name"</span>, <span class="hljs-string">"respData"</span>);
        context.<span class="hljs-keyword">put</span>(<span class="hljs-string">"data_description"</span>, <span class="hljs-string">"返回的数据，没有则返回{}"</span>);
    }
</code></pre>
                        <h2>第三方类返回</h2>
                        <p>如果有个一个PageInfo&lt;T&gt;类，是第三方jar中的，没办法对其修改，那要如何对它里面的属性编写对应文档呢。</p>
                        <p>PageInfo&lt;T&gt;类内容如下：</p>
                        <pre><code class="hljs"><span class="hljs-comment">// 假设这是jar中的类，没法修改。但是要对其进行文档生成</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PageInfo</span>&lt;<span class="hljs-title">T</span>&gt; </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> pageIndex;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> pageSize;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">long</span> total;
    <span class="hljs-comment">// 省略 get set</span>
}
</code></pre>
                        <p>我们可以显式的声明字段信息：</p>
                        <pre><code class="hljs">    <span class="hljs-variable">@Api</span>(name = <span class="hljs-string">"goods.pageinfo"</span>, version = <span class="hljs-string">"1.0"</span>)
    <span class="hljs-variable">@ApiDocMethod</span>(description=<span class="hljs-string">"获取商品列表"</span>
            ,results={<span class="hljs-variable">@ApiDocField</span>(name=<span class="hljs-string">"pageIndex"</span>,description=<span class="hljs-string">"第几页"</span>,dataType=DataType.INT,example=<span class="hljs-string">"1"</span>),
                    <span class="hljs-variable">@ApiDocField</span>(name=<span class="hljs-string">"pageSize"</span>,description=<span class="hljs-string">"每页几条数据"</span>,dataType=DataType.INT,example=<span class="hljs-string">"10"</span>),
                    <span class="hljs-variable">@ApiDocField</span>(name=<span class="hljs-string">"total"</span>,description=<span class="hljs-string">"每页几条数据"</span>,dataType=DataType.LONG,example=<span class="hljs-string">"100"</span>),
                    <span class="hljs-variable">@ApiDocField</span>(name=<span class="hljs-string">"rows"</span>,description=<span class="hljs-string">"数据"</span>,dataType=DataType.ARRAY,elementClass=Goods.class),}
            )
    public PageInfo&lt;Goods&gt; <span class="hljs-function">pageinfo</span>(GoodsParam param) {

    }
</code></pre>
                        <h2>文档模型复用</h2>
                        <p>如果多个接口都返回PageInfo&lt;Goods&gt;，需要复制黏贴大量的注解，改一个地方需要改多个接口，无法达到复用效果。我们可以新建一个GoodsPageVo继承PageInfo，然后把文档注解写在类的头部，这样可以达到复用效果。</p>
                        <pre><code class="hljs"><span class="hljs-variable">@ApiDocBean</span>(fields = {
    <span class="hljs-variable">@ApiDocField</span>(name=<span class="hljs-string">"pageIndex"</span>,description=<span class="hljs-string">"第几页"</span>,dataType=DataType.INT,example=<span class="hljs-string">"1"</span>),
    <span class="hljs-variable">@ApiDocField</span>(name=<span class="hljs-string">"pageSize"</span>,description=<span class="hljs-string">"每页几条数据"</span>,dataType=DataType.INT,example=<span class="hljs-string">"10"</span>),
    <span class="hljs-variable">@ApiDocField</span>(name=<span class="hljs-string">"total"</span>,description=<span class="hljs-string">"每页几条数据"</span>,dataType=DataType.LONG,example=<span class="hljs-string">"100"</span>),
    <span class="hljs-variable">@ApiDocField</span>(name=<span class="hljs-string">"rows"</span>,description=<span class="hljs-string">"商品列表"</span>,dataType=DataType.ARRAY,elementClass=Goods.class),
})
public class GoodsPageVo extends PageInfo&lt;Goods&gt; {

}
</code></pre>
                        <pre><code class="hljs">    <span class="hljs-variable">@Api</span>(name = <span class="hljs-string">"goods.pageinfo"</span>, version = <span class="hljs-string">"2.0"</span>)
    <span class="hljs-variable">@ApiDocMethod</span>(description=<span class="hljs-string">"获取商品列表"</span>,resultClass=GoodsPageVo.class)
    public PageInfo&lt;Goods&gt; <span class="hljs-function">pageinfo2</span>(GoodsParam param) {

    }
</code></pre>
                        <p>使用resultClass=GoodsPageVo.class指定返回结果类型即可。</p>
                        <h2>更改文档显示位置（v1.9.1）</h2>
                        <p>可以使用<code>order</code>属性来指定文档显示位置，值越小越靠前</p>
                        <pre><code class="hljs"><span class="hljs-variable">@ApiDoc</span>(value = <span class="hljs-string">"文档demo，参考DocDemoApi.java"</span>, order = <span class="hljs-number">1</span>)

<span class="hljs-variable">@ApiDocMethod</span>(description = <span class="hljs-string">"第一个"</span>,  order = <span class="hljs-number">1</span> <span class="hljs-comment">// 指定了order，优先按这个值排序</span>
    )

若不指定order，则按接口名排序
</code></pre>
                        <h1>使用oauth2</h1>
                        <p>如果第三方应用和本开放平台对接时需要获取用户隐私数据（如商品、订单），为为了安全与隐私，第三方应用需要取得用户的授权，即获取访问用户数据的授权令牌 AccessToken 。这种情况下，第三方应用需要引导用户完成帐号“登录授权”的流程。</p>
                        <p>easyopen从1.2.0版本开始支持oauth2认证。接入方式很简单：</p>
                        <ol>
                            <li>新建一个Oauth2ManagerImpl类，实现Oauth2Manager接口</li>
                            <li>用户类实现OpenUser接口。</li>
                        </ol>
                        <pre><code class="hljs">@Service
public class Oauth2ManagerImpl implements Oauth2Manager {
<span class="hljs-keyword">...</span>
}

public class User implements OpenUser {
<span class="hljs-keyword">...</span>
}
</code></pre>
                        <p>因为对于accessToken的管理每个开发人员所用的方式都不一样，所以需要自己来实现。</p>
                        <ul>
                            <li>Oauth2Manager接口定义如下：</li>
                        </ul>
                        <pre><code class="hljs">public interface Oauth2Manager {

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 添加 auth code
     <span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param authCode</span>
     <span class="hljs-keyword">*</span>            code值
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param authUser</span>
     <span class="hljs-keyword">*</span>            用户
     <span class="hljs-keyword">*</span>/
    void addAuthCode(String authCode, OpenUser authUser);

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 添加 access token
     <span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param accessToken</span>
     <span class="hljs-keyword">*</span>            token值
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param authUser</span>
     <span class="hljs-keyword">*</span>            用户
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param expiresIn 时长,秒</span>
     <span class="hljs-keyword">*</span>/
    void addAccessToken(String accessToken, OpenUser authUser, long expiresIn);

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 验证auth code是否有效
     <span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param authCode</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@return 无效返回false</span>
     <span class="hljs-keyword">*</span>/
    boolean checkAuthCode(String authCode);

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 根据auth code获取用户
     <span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param authCode</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@return 返回用户</span>
     <span class="hljs-keyword">*</span>/
    OpenUser getUserByAuthCode(String authCode);

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 根据access token获取用户名
     <span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param accessToken</span>
     <span class="hljs-keyword">*</span>            token值
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@return 返回用户</span>
     <span class="hljs-keyword">*</span>/
    OpenUser getUserByAccessToken(String accessToken);

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 获取auth code / access token 过期时间
     <span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@return</span>
     <span class="hljs-keyword">*</span>/
    long getExpireIn(ApiConfig apiConfig);

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 用户登录，需判断是否已经登录
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param request</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@return 返回用户对象</span>
     <span class="hljs-keyword">*</span>/
    OpenUser login(HttpServletRequest request) throws LoginErrorException;
}
</code></pre>
                        <h2>accessToken获取流程</h2>
                        <ul>
                            <li>第一步获取code</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-keyword">1</span>、首先通过如http://localhost:8080/api/authorize?client_id=test&amp;response_type=code&amp;redirect_uri=http<span class="hljs-number">%3</span>A<span class="hljs-number">%2</span>F<span class="hljs-number">%2</span>Flocalhost<span class="hljs-number">%3</span>A8080<span class="hljs-number">%2</span>Foauth2callback访问授权页面；
<span class="hljs-keyword">2</span>、该控制器首先检查clientId是否正确；如果错误将返回相应的错误信息；
<span class="hljs-keyword">3</span>、然后判断用户是否登录了，如果没有登录首先到登录页面登录；
<span class="hljs-keyword">4</span>、登录成功后生成相应的code即授权码，然后重定向到客户端地址，如http://localhost:8080/oauth2callback?code=6d250650831fea227749f49a5b49ccad；在重定向到的地址中会带上code参数（授权码），接着客户端可以根据授权码去换取accessToken。
</code></pre>
                        <ul>
                            <li>第二步通过code换取accessToken</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-number">1</span>、首先通过如<span class="hljs-string">http:</span><span class="hljs-comment">//localhost:8080/api/accessToken，POST提交如下数据访问:</span>
<span class="hljs-label">
        code:</span><span class="hljs-number">6</span>d250650831fea227749f49a5b49ccad
<span class="hljs-label">        client_id:</span>test
<span class="hljs-label">        client_secret:</span><span class="hljs-number">123456</span>
<span class="hljs-label">        grant_type:</span>authorization_code
<span class="hljs-label">        redirect_uri:</span><span class="hljs-string">http:</span><span class="hljs-comment">//localhost:8080/api/authorize</span>

<span class="hljs-number">2</span>、服务器会验证client_id、client_secret、code的正确性，如果错误会返回相应的错误；
<span class="hljs-number">3</span>、如果验证通过会生成并返回相应的访问令牌accessToken。

{
  <span class="hljs-string">"access_token"</span>: <span class="hljs-string">"01e111c0d3c8e415fea038d5c64432ef"</span>,
  <span class="hljs-string">"refresh_token"</span>: <span class="hljs-string">"d1165b75d386b3ef1bd0423b4e3bfef9"</span>,
  <span class="hljs-string">"token_type"</span>: <span class="hljs-string">"Bearer"</span>,
  <span class="hljs-string">"expires_in"</span>: <span class="hljs-number">7200</span>,
  <span class="hljs-string">"username"</span>: <span class="hljs-string">"admin"</span>
}
</code></pre>
                        <p>以上两个步骤需要在客户端上实现。示例项目easyopen-server上有个例子可以参考，启动服务，然后访问http://localhost:8080/go_oauth2</p>
                        <p>获取accessToken用户：</p>
                        <pre><code class="hljs"><span class="hljs-comment">// 拿到accessToken用户</span>
<span class="hljs-constant">OpenUser user</span> = ApiContext.getAccessTokenUser();
</code></pre>
                        <h2>使用refreshToken刷新accessToken</h2>
                        <p>accessToken有过期时间，为了防止过期可以通过refreshToken来换取新的accessToken，方便后续接口调用。</p>
                        <pre><code class="hljs"><span class="hljs-number">1</span>. 首先通过如http://localhost:<span class="hljs-number">8080</span>/api/accessToken，POST提交如下数据访问:
        refresh_token:你的refreshToken
        client_id:<span class="hljs-built_in">test</span>
        client_secret:<span class="hljs-number">123456</span>
        grant_<span class="hljs-built_in">type</span>:refresh_token
<span class="hljs-number">2</span>. 服务器会验证client_id、client_secret、refresh_token的正确性，如果错误会返回相应的错误；
<span class="hljs-number">3</span>. 如果验证通过会生成并返回新的访问令牌accessToken和新的refreshToken

返回结果：
{
  <span class="hljs-string">"access_token"</span>: <span class="hljs-string">"01e111c0d3c8e415fea038d5c64432ef"</span>,
  <span class="hljs-string">"refresh_token"</span>: <span class="hljs-string">"d1165b75d386b3ef1bd0423b4e3bfef9"</span>,
  <span class="hljs-string">"token_type"</span>: <span class="hljs-string">"Bearer"</span>,
  <span class="hljs-string">"expires_in"</span>: <span class="hljs-number">7200</span>,
  <span class="hljs-string">"username"</span>: <span class="hljs-string">"admin"</span>
}

</code></pre>
                        <p>成功换取新的accessToken和refreshToken后，老的accessToken和refreshToken不能使用。</p>
                        <h1>使用JWT</h1>
                        <p>JWT的介绍参考这里：<a href="https://www.jianshu.com/p/576dbf44b2ae">什么是 JWT – JSON WEB TOKEN</a>。</p>
                        <p>之前我们的web应用使用session来维持用户与服务器之间的关系，其原理是使用一段cookie字符与服务器中的一个Map来对应，Map&lt;cookie,UserInfo&gt;，用户每次交互需要带一个sessionid过来。如果不使用分布式session，一旦服务器重启session会丢失，这样会影响用户体验，甚至影响业务逻辑。如果把用户信息存在客户端就没这个问题了。</p>
                        <p>easyopen创建JWT方式如下：</p>
                        <pre><code class="hljs"><span class="hljs-built_in">Map</span><span class="hljs-subst">&lt;</span><span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span><span class="hljs-subst">&gt;</span> <span class="hljs-built_in">data</span> <span class="hljs-subst">=</span> <span class="hljs-literal">new</span> HashMap<span class="hljs-subst">&lt;&gt;</span>();
<span class="hljs-built_in">data</span><span class="hljs-built_in">.</span>put(<span class="hljs-string">"id"</span>, user<span class="hljs-built_in">.</span>getId()<span class="hljs-built_in">.</span>toString());
<span class="hljs-built_in">data</span><span class="hljs-built_in">.</span>put(<span class="hljs-string">"username"</span>, user<span class="hljs-built_in">.</span>getUsername());

<span class="hljs-built_in">String</span> jwt <span class="hljs-subst">=</span> ApiContext<span class="hljs-built_in">.</span>createJwt(<span class="hljs-built_in">data</span>);
</code></pre>
                        <p>这段代码用在用户登录的时候，然后把jwt返回给客户端，让客户端保存，如H5可以存在localStorage中。</p>
                        <p>客户端传递jwt方式：</p>
                        <pre><code class="hljs"><span class="hljs-keyword">method</span>.setRequestHeader(<span class="hljs-string">"Authorization"</span>, <span class="hljs-string">"Bearer "</span> + jwt);
</code></pre>
                        <p>即在header头部添加一个Authorization，内容是"Bearer " + jwt</p>
                        <p>客户端请求过来后，服务端获取jwt中的数据：</p>
                        <pre><code class="hljs"><span class="hljs-comment">// 获取jwt数据</span>
<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, Claim&gt; jwtData = ApiContext.getJwtData();
</code></pre>
                        <h1>拦截器</h1>
                        <p>easyopen在1.3.1版本开始支持拦截器。</p>
                        <p>easyopen拦截器实现原理跟springmvc拦截器类似，拦截器作用在api方法上，即有@Api注解的方法。</p>
                        <p>拦截器定义如下：</p>
                        <pre><code class="hljs">/<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
 <span class="hljs-keyword">*</span> 拦截器，原理同springmvc拦截器
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@author tanghc</span>
 <span class="hljs-keyword">*</span>
 <span class="hljs-keyword">*</span>/
public interface ApiInterceptor {
    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 预处理回调方法，在方法调用前执行
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param request</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param response</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param serviceObj service类</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param argu 方法参数</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@return</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@throws Exception</span>
     <span class="hljs-keyword">*</span>/
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object serviceObj, Object argu)
            throws Exception;

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 接口方法执行完后调用此方法。
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param request</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param response</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param serviceObj service类</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param argu 参数</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param result 方法返回结果</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@throws Exception</span>
     <span class="hljs-keyword">*</span>/
    void postHandle(HttpServletRequest request, HttpServletResponse response, Object serviceObj, Object argu,
            Object result) throws Exception;

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 结果包装完成后执行
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param request</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param response</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param serviceObj service类</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param argu 参数</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param result 最终结果，被包装过</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param e </span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@throws Exception</span>
     <span class="hljs-keyword">*</span>/
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object serviceObj, Object argu,
            Object result, Exception e) throws Exception;

    /<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span> 匹配拦截器
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@param apiMeta 接口信息</span>
     <span class="hljs-keyword">*</span> <span class="hljs-comment">@return</span>
     <span class="hljs-keyword">*</span>/
    boolean match(ApiMeta apiMeta);
}
</code></pre>
                        <p>同样也提供一个适配器ApiInterceptorAdapter</p>
                        <ul>
                            <li>拦截器执行流程：</li>
                        </ul>
                        <p>跟springmvc拦截器执行流程一样</p>
                        <ol>
                            <li>preHandle 如果返回false，则不调用api方法，接着逆序调用afterCompletion，需要通过response自定义返回</li>
                            <li>如果preHandle 返回true，继续进行下一个preHandle</li>
                            <li>preHandle执行完毕后，逆序执行postHandle</li>
                            <li>最后逆序调用afterCompletion</li>
                        </ol>
                        <ul>
                            <li>正常流程：</li>
                        </ul>
                        <pre><code class="hljs">ApiInterceptor1<span class="hljs-class">.preHandle</span>
ApiInterceptor2<span class="hljs-class">.preHandle</span>

apiMethod.<span class="hljs-function"><span class="hljs-title">invoke</span><span class="hljs-params">()</span></span> <span class="hljs-comment">// api方法调用</span>

ApiInterceptor2<span class="hljs-class">.postHandle</span>
ApiInterceptor1<span class="hljs-class">.postHandle</span>

ApiInterceptor2<span class="hljs-class">.afterCompletion</span>
ApiInterceptor1<span class="hljs-class">.afterCompletion</span>
</code></pre>
                        <h2>配置拦截器</h2>
                        <p>新建一个日志处理拦截器，继承ApiInterceptorAdapter，重写父类中的方法</p>
                        <pre><code class="hljs">public class <span class="hljs-type">LogInterceptor</span> extends <span class="hljs-type">ApiInterceptorAdapter</span> {

    @<span class="hljs-type">Override</span>
    public boolean preHandle(<span class="hljs-type">HttpServletRequest</span> request, <span class="hljs-type">HttpServletResponse</span> response, <span class="hljs-type">Object</span> serviceObj, <span class="hljs-type">Object</span> argu)
            throws <span class="hljs-type">Exception</span> {

        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"======preHandle======"</span>);
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"IP:"</span> + <span class="hljs-type">RequestUtil</span>.getClientIP(request));
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"接口类："</span> + serviceObj.getClass().getName());
        <span class="hljs-keyword">if</span>(argu != null) {
            <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"参数类："</span> + argu.getClass().getName());
        }


        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }

    @<span class="hljs-type">Override</span>
    public <span class="hljs-type">void</span> postHandle(<span class="hljs-type">HttpServletRequest</span> request, <span class="hljs-type">HttpServletResponse</span> response, <span class="hljs-type">Object</span> serviceObj, <span class="hljs-type">Object</span> argu,
            <span class="hljs-type">Object</span> <span class="hljs-literal">result</span>) throws <span class="hljs-type">Exception</span> {
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"======postHandle======"</span>);
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"接口类："</span> + serviceObj.getClass().getName());
        <span class="hljs-keyword">if</span>(argu != null) {
            <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"参数类："</span> + argu.getClass().getName());
        }
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"结果："</span> + <span class="hljs-type">JSON</span>.toJSONString(<span class="hljs-literal">result</span>));
    }

    @<span class="hljs-type">Override</span>
    public <span class="hljs-type">void</span> afterCompletion(<span class="hljs-type">HttpServletRequest</span> request, <span class="hljs-type">HttpServletResponse</span> response, <span class="hljs-type">Object</span> serviceObj,
            <span class="hljs-type">Object</span> argu, <span class="hljs-type">Object</span> <span class="hljs-literal">result</span>, <span class="hljs-type">Exception</span> e) throws <span class="hljs-type">Exception</span> {
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"======afterCompletion======"</span>);
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"接口类："</span> + serviceObj.getClass().getName());
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"参数类："</span> + argu.getClass().getName());
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"最终结果："</span> + <span class="hljs-type">JSON</span>.toJSONString(<span class="hljs-literal">result</span>));
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"e:"</span> + e);
    }

}
</code></pre>
                        <p>在apiConfgi中添加拦截器:</p>
                        <pre><code class="hljs">@Override
    protected void initApiConfig(ApiConfig apiConfig) {
        <span class="hljs-keyword">...</span>

        // 配置拦截器
        apiConfig.setInterceptors(
                new ApiInterceptor[] { new LogInterceptor()});

       <span class="hljs-keyword">...</span>
    }
</code></pre>
                        <h2>拦截范围</h2>
                        <p>默认拦截所有接口，如果要拦截指定接口，可重写boolean match()方法：</p>
                        <pre><code class="hljs"><span class="hljs-comment">// 只拦截goods.get接口</span>
<span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">match</span><span class="hljs-params">(ApiMeta apiMeta)</span> </span>{
        <span class="hljs-keyword">return</span> apiMeta.getName().equals(<span class="hljs-string">"goods.get"</span>);
    }
</code></pre>
                        <h1>数据加密传输</h1>
                        <p>默认我们的数据传输都是不经过加密的，要加密传输的话得用上HTTPS协议。easyopen在1.4.0版本开始提供了数据加密传输，不需要HTTPS协议。</p>
                        <p>easyopen基于公私钥+AES加密传输，交互流程如下：</p>
                        <pre><code class="hljs"><span class="hljs-number">0</span>. 事先把公钥放在客户端,私钥放在服务端
<span class="hljs-number">1</span>. 客户端生成一个随机码<span class="hljs-keyword">random</span>Key
<span class="hljs-number">2</span>. 将<span class="hljs-keyword">random</span>Key通过公钥RSA加密str = rsa_encode(<span class="hljs-keyword">random</span>Key,publicKey)
<span class="hljs-number">3</span>. 将str发送到服务端
<span class="hljs-number">4</span>. 服务端通过私钥解开str,得到<span class="hljs-keyword">random</span>Key:<span class="hljs-keyword">random</span>Key = rsa_decode(str, privateKey)
<span class="hljs-number">5</span>. 服务端使用<span class="hljs-keyword">random</span>Key通过AES对称加密一个值(假设值为<span class="hljs-string">"0"</span>)返回给客户端,resp = aes_encode(<span class="hljs-string">"0"</span>, <span class="hljs-keyword">random</span>Key)
<span class="hljs-number">6</span>. 客户端用自己的<span class="hljs-keyword">random</span>Key去aes解密resp,如果得到的是<span class="hljs-string">"0"</span>,则整个握手交互完成,后续都用这个<span class="hljs-keyword">random</span>Key进行aes加解密传输

注:
黑客是可以拿到公钥的,但是黑客无法知道客户端生成的随机码<span class="hljs-keyword">random</span>Key,一旦str发生改变则握手失败.
整个过程的重点就是将<span class="hljs-keyword">random</span>Key安全的送到服务器,后期都用<span class="hljs-keyword">random</span>Key进行对称加密传输,对称加密黑客无法破解.
此流程参照HTTPS,只不过服务器无法将公钥正确的送到客户端(浏览器),因此浏览器的HTTPS需要CA机构介入.
</code></pre>
                        <h2>公私钥配置</h2>
                        <ul>
                            <li>生成公私钥</li>
                        </ul>
                        <pre><code class="hljs">public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PubPriKeyTest</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">TestCase</span> {</span>
    <span class="hljs-comment">/**
     * 生成公私钥
     * @throws Exception
     */</span>
    <span class="hljs-annotation">@Test</span>
    public void testCreate() <span class="hljs-keyword">throws</span> <span class="hljs-type">Exception</span> {
        <span class="hljs-type">KeyStore</span> store = <span class="hljs-type">RSAUtil</span>.createKeys();
        <span class="hljs-type">System</span>.out.println(<span class="hljs-string">"公钥:"</span>);
        <span class="hljs-type">System</span>.out.println(store.getPublicKey());
        <span class="hljs-type">System</span>.out.println(<span class="hljs-string">"私钥:"</span>);
        <span class="hljs-type">System</span>.out.println(store.getPrivateKey());
    }
}
</code></pre>
                        <ul>
                            <li>在客户端新建一个pub.key文件，放入公钥字符串，pub.key放在客户端，启动的时候加载。</li>
                            <li>在服务端新建一个pri.key文件，放入私钥字符串，pri.key放在resources目录下，服务启动会自动读取。</li>
                        </ul>
                        <p>easyopen-sdk的resources下已经存放了一个pub.key，实例化一个EncryptClient对象就能使用数据加密传输。感兴趣的同学可以查看源码，了解整个交互流程。</p>
                        <pre><code class="hljs"><span class="hljs-comment">// 数据加密传输不需要secret</span>
OpenClient <span class="hljs-keyword">client</span> = <span class="hljs-keyword">new</span> EncryptClient(url, app_key);
</code></pre>
                        <h1>可自定义默认版本号</h1>
                        <p>接口默认的版本号是空字符串""，如果要修改，可进行如下配置：</p>
                        <pre><code class="hljs"><span class="hljs-comment">// 修改默认版本号，所有的接口的默认版本变为1.0</span>
apiConifg.setDefaultVersion(<span class="hljs-string">"1.0"</span>);
</code></pre>
                        <h1>自定义session管理</h1>
                        <p>easyopen1.4.0开始支持。</p>
                        <ul>
                            <li>创建session</li>
                        </ul>
                        <p>登陆成功后创建session，并返回sessionId</p>
                        <pre><code class="hljs"><span class="hljs-comment">// 自定义session</span>
    <span class="hljs-annotation">@PostMapping</span>(<span class="hljs-string">"managedSessionLogin"</span>)
    <span class="hljs-keyword">public</span> <span class="hljs-function">String <span class="hljs-title">managedSessionLogin</span><span class="hljs-params">(HttpServletRequest request)</span> </span>{
        <span class="hljs-comment">// 假设登陆成功,创建一个sessionId返回给客户端</span>
        SessionManager sessionManager = ApiContext.getApiConfig().getSessionManager();
        HttpSession session = sessionManager.getSession(<span class="hljs-keyword">null</span>);
        session.setAttribute(<span class="hljs-string">"username"</span>, <span class="hljs-string">"tom"</span>);

        <span class="hljs-function"><span class="hljs-keyword">return</span> session.<span class="hljs-title">getId</span><span class="hljs-params">()</span></span>;
    }
</code></pre>
                        <ul>
                            <li>使用session</li>
                        </ul>
                        <p>客户端需要传递access_token参数，值为sessionId</p>
                        <p>请求参数：</p>
                        <pre><code class="hljs">{
	"<span class="hljs-attribute">access_token</span>": <span class="hljs-value"><span class="hljs-string">"4191FB1D8356495D98ECCF91C615A530"</span></span>,
	"<span class="hljs-attribute">app_key</span>": <span class="hljs-value"><span class="hljs-string">"test"</span></span>,
	"<span class="hljs-attribute">data</span>": <span class="hljs-value"><span class="hljs-string">"{}"</span></span>,
	"<span class="hljs-attribute">sign</span>": <span class="hljs-value"><span class="hljs-string">"F7AB6BC059DFCA93CA2328C9BAF236BA"</span></span>,
	"<span class="hljs-attribute">sign_method</span>": <span class="hljs-value"><span class="hljs-string">"md5"</span></span>,
	"<span class="hljs-attribute">name</span>": <span class="hljs-value"><span class="hljs-string">"manager.session.get"</span></span>,
	"<span class="hljs-attribute">format</span>": <span class="hljs-value"><span class="hljs-string">"json"</span></span>,
	"<span class="hljs-attribute">version</span>": <span class="hljs-value"><span class="hljs-string">""</span></span>,
	"<span class="hljs-attribute">timestamp</span>": <span class="hljs-value"><span class="hljs-string">"2018-03-13 13:48:45"</span>
</span>}
</code></pre>
                        <p>服务端通过HttpSession session = ApiContext.getManagedSession();获取session</p>
                        <pre><code class="hljs">@Api(name = <span class="hljs-string">"manager.session.get"</span>)
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">Object</span> managersetsession() {
        HttpSession session = ApiContext.getManagedSession();

        System.out.<span class="hljs-built_in">println</span>(session.getId());

        <span class="hljs-keyword">Object</span> user = session.getAttribute(<span class="hljs-string">"username"</span>);

        <span class="hljs-keyword">return</span> user;
    }
</code></pre>
                        <h2>使用redis管理session</h2>
                        <p>easyopen默认使用谷歌的guava缓存进行session管理，但是在集群的情况下会有问题，因此easyopen还提供了一个Redis版本。配置如下：</p>
                        <ul>
                            <li>pom添加redis依赖</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-tag">&lt;<span class="hljs-title">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-title">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-title">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-title">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="hljs-tag">&lt;/<span class="hljs-title">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-title">dependency</span>&gt;</span>
</code></pre>
                        <ul>
                            <li>添加redis参数</li>
                        </ul>
                        <pre><code class="hljs">
<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#####redis基础配置###</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">##</span>
spring.redis.database=<span class="hljs-number">0</span>
spring.redis.host=<span class="hljs-number">10.1</span>.11.48
spring.redis.password=<span class="hljs-number">0987654321</span>rfvujmtgbyhn
spring.redis.port=<span class="hljs-number">6379</span>
<span class="hljs-comment"># 连接超时时间 单位 ms（毫秒）</span>
spring.redis.timeout=<span class="hljs-number">3000</span>

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#####redis线程池设置###</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">##</span>
<span class="hljs-comment"># 连接池中的最大空闲连接，默认值也是8。</span>
spring.redis.pool.max-idle=<span class="hljs-number">500</span>
<span class="hljs-comment">#连接池中的最小空闲连接，默认值也是0。</span>
spring.redis.pool.min-idle=<span class="hljs-number">50</span>
<span class="hljs-comment"># 如果赋值为-1，则表示不限制；如果pool已经分配了maxActive个jedis实例，则此时pool的状态为exhausted(耗尽)。</span>
spring.redis.pool.max-active=<span class="hljs-number">2000</span>
<span class="hljs-comment"># 等待可用连接的最大时间，单位毫秒，默认值为-1，表示永不超时。如果超过等待时间，则直接抛出JedisConnectionException</span>
spring.redis.pool.max-wait=<span class="hljs-number">1000</span>
</code></pre>
                        <ul>
                            <li>设置apiConfig</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-annotation">@Controller</span>
<span class="hljs-annotation">@RequestMapping</span>(value = <span class="hljs-string">"/api"</span>)
public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexController</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">ApiController</span> {</span>

    <span class="hljs-annotation">@Autowired</span>
    <span class="hljs-keyword">private</span> <span class="hljs-type">RedisTemplate</span> redisTemplate; <span class="hljs-comment">// 1 声明redis模板</span>

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">protected</span> void initApiConfig(<span class="hljs-type">ApiConfig</span> apiConfig) {
        <span class="hljs-comment">// 配置秘钥键值对</span>
        <span class="hljs-type">Map</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">String</span>&gt; appSecretStore = <span class="hljs-keyword">new</span> <span class="hljs-type">HashMap</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">String</span>&gt;();
        appSecretStore.put(<span class="hljs-string">"test"</span>, <span class="hljs-string">"123456"</span>);

        <span class="hljs-comment">// 2 配置sessionManager</span>
        <span class="hljs-type">RedisSessionManager</span> sessionManager = <span class="hljs-keyword">new</span> <span class="hljs-type">RedisSessionManager</span>(redisTemplate);
        apiConfig.setSessionManager(sessionManager);

        apiConfig.addAppSecret(appSecretStore);
    }

}
</code></pre>
                        <h3>修改redis的key前缀</h3>
                        <p>默认存入redis的key前缀为<code>session:</code>，如果要自定义前缀可调用：</p>
                        <pre><code class="hljs">sessionManager.<span class="hljs-built_in">set</span>KeyPrefix(<span class="hljs-string">"session-key:"</span>);
</code></pre>
                        <h1>app_key和secret存放在数据库或redis中</h1>
                        <p>这里以redis为例</p>
                        <p>新建一个RedisAppSecretManager类实现AppSecretManager接口</p>
                        <pre><code class="hljs"><span class="hljs-javadoc">/**
 * 使用方式:
 *
 * &lt;pre&gt;<span class="hljs-javadoctag">
@Autowired</span>
private AppSecretManager appSecretManager;
    <span class="hljs-javadoctag">
@Override</span>
protected void initApiConfig(ApiConfig apiConfig) {
    ...
    apiConfig.setAppSecretManager(appSecretManager);
    ...
}

 * &lt;/pre&gt;
 *
 *<span class="hljs-javadoctag"> @author</span> tanghc
 *
 */</span>
<span class="hljs-annotation">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisAppSecretManager</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">AppSecretManager</span> </span>{

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String APP_KEY_PREFIX = <span class="hljs-string">"easyopen_app_key:"</span>;

    <span class="hljs-annotation">@Autowired</span>
    <span class="hljs-keyword">private</span> StringRedisTemplate stringRedisTemplate;


    <span class="hljs-annotation">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addAppSecret</span><span class="hljs-params">(Map&lt;String, String&gt; appSecretStore)</span> </span>{
        stringRedisTemplate.opsForHash().putAll(APP_KEY_PREFIX, appSecretStore);
    }

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getSecret</span><span class="hljs-params">(String appKey)</span> </span>{
        <span class="hljs-keyword">return</span> (String)stringRedisTemplate.opsForHash().get(APP_KEY_PREFIX, appKey);
    }

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isValidAppKey</span><span class="hljs-params">(String appKey)</span> </span>{
        <span class="hljs-keyword">if</span> (appKey == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
        }
        <span class="hljs-keyword">return</span> getSecret(appKey) != <span class="hljs-keyword">null</span>;
    }

}
</code></pre>
                        <p>存放app_key和secret采用hash set的方式,这样在redis中查看会比较方便,一目了然.</p>
                        <p>然后在IndexController中:</p>
                        <pre><code class="hljs">@Autowired
private AppSecretManager appSecretManager;

@Override
protected void initApiConfig(ApiConfig apiConfig) {
    <span class="hljs-keyword">...</span>
    apiConfig.setAppSecretManager(appSecretManager);
    <span class="hljs-keyword">...</span>
}
</code></pre>
                        <h1>使用WebFlux</h1>
                        <p>这里基于springboot2 + WebFlux,相关教程见:<a href="https://www.ibm.com/developerworks/cn/java/spring5-webflux-reactive/index.html">springboot-webflux</a></p>
                        <p>需要easyopen1.7.0及以上版本</p>
                        <ul>
                            <li>在pom.xml中添加WebFlux依赖</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-tag">&lt;<span class="hljs-title">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-title">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">artifactId</span>&gt;</span>spring-boot-starter-webflux<span class="hljs-tag">&lt;/<span class="hljs-title">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">dependency</span>&gt;</span>
</code></pre>
                        <ul>
                            <li>在IndexController中添加:</li>
                        </ul>
                        <pre><code class="hljs">
@Controller
@RequestMapping(<span class="hljs-string">"/api"</span>)
public class IndexController extends ApiController {
    <span class="hljs-keyword">...</span>

    // http://localhost:<span class="hljs-number">8080</span>/api/mono
    @RequestMapping(<span class="hljs-string">"mono"</span>)
    @ResponseBody
    public Mono&lt;Object&gt; mono(HttpServletRequest request, HttpServletResponse response) {
        <span class="hljs-keyword">return</span> Mono.justOrEmpty(this.invoke(request, response));
    }

    <span class="hljs-keyword">...</span>

}
</code></pre>
                        <p>api的url由之前的http://localhost:8080/api变为http://localhost:8080/api/mono</p>
                        <p>其它地方不变</p>
                        <h1>开启app对接模式（v1.7.5）</h1>
                        <p>如果平台直接跟Android或IOS对接，可开启这个功能。因为手机上的时间有可能跟服务端的时间不一致（用户的手机情况不可控）。</p>
                        <p>失去了时间校验，一个请求有可能被反复调用，服务端需要防止重复提交，有必要的话上HTTPS。</p>
                        <p>开启方式：</p>
                        <pre><code class="hljs">apiConfig.openAppMode<span class="hljs-literal">()</span>;
</code></pre>
                        <p>开启app对接模式，开启后不进行timeout校验。</p>
                        <h1>防止表单重复提交（v1.7.7）</h1>
                        <p>使用redis分布式锁解决表单重复提交问题。</p>
                        <pre><code class="hljs">核心思想：

<span class="hljs-tag">try</span> {
    锁（用户<span class="hljs-tag">id</span> + 接口名） {
        执行业务代码
    }
} <span class="hljs-tag">finally</span> {
    释放锁
}

在锁的内部执行业务代码时，其它线程进来都将拒之门外。

</code></pre>
                        <p>新增拦截器继承BaseLockInterceptor</p>
                        <pre><code class="hljs"><span class="hljs-comment">/**
 * 使用分布式锁防止表单重复提交
 *
 * @author tanghc
 */</span>
public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LockInterceptor</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">BaseLockInterceptor</span> {</span>

    <span class="hljs-keyword">private</span> <span class="hljs-type">StringRedisTemplate</span> redisTemplate;

    public <span class="hljs-type">LockInterceptor</span>() {
        redisTemplate = <span class="hljs-type">ApiContext</span>.getApplicationContext().getBean(<span class="hljs-type">StringRedisTemplate</span>.<span class="hljs-keyword">class</span>);
    }

    <span class="hljs-annotation">@SuppressWarnings</span>(<span class="hljs-string">"rawtypes"</span>)
    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">protected</span> <span class="hljs-type">RedisTemplate</span> getRedisTemplate() {
        <span class="hljs-keyword">return</span> redisTemplate;
    }

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">protected</span> <span class="hljs-type">String</span> getUserId() {
        <span class="hljs-type">Map</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">Claim</span>&gt; jwtData = <span class="hljs-type">ApiContext</span>.getJwtData();
        <span class="hljs-type">String</span> id = jwtData.get(<span class="hljs-string">"id"</span>).asString(); <span class="hljs-comment">// 用户id</span>
        <span class="hljs-keyword">return</span> id;
    }

    <span class="hljs-annotation">@Override</span>
    public boolean <span class="hljs-keyword">match</span>(<span class="hljs-type">ApiMeta</span> apiMeta) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"userlock.test"</span>.equals(apiMeta.getName()); <span class="hljs-comment">// 只针对这个接口</span>
    }
}
</code></pre>
                        <p>实现上面三个方法即可,match方法返回true表示执行这个拦截器，可针对特定的接口做操作。</p>
                        <p>然后配置拦截器：</p>
                        <pre><code class="hljs">apiConfig.setInterceptors(<span class="hljs-keyword">new</span> <span class="hljs-type">ApiInterceptor</span><span class="hljs-literal">[]</span> {<span class="hljs-keyword">new</span> <span class="hljs-type">LockInterceptor</span><span class="hljs-literal">()</span>});
</code></pre>
                        <h1>监控页面（v1.8.1）</h1>
                        <p>启动服务端，浏览器访问:<a href="http://localhost:8080/api/monitor">http://localhost:8080/api/monitor</a></p>
                        <p>输入密码:monitor123</p>
                        <h2>修改默认密码</h2>
                        <pre><code class="hljs">apiConfig.<span class="hljs-built_in">set</span>MonitorPassword(newPassword);
</code></pre>
                        <ul>
                            <li>监控内容放在Map对象中，存放接口对应的累积信息，并不会记录每次请求的信息，因此无需担心内存使用量增多。</li>
                        </ul>
                        <h1>文件上传（v1.8.7）</h1>
                        <ul>
                            <li>SDK</li>
                        </ul>
                        <pre><code class="hljs">/**
 * 上传文件，读取本地文件
 *
 * @throws <span class="hljs-type">IOException</span>
 */
@<span class="hljs-type">Test</span>
public <span class="hljs-type">void</span> testUpload() throws <span class="hljs-type">IOException</span> {
    <span class="hljs-type">GoodsParam</span> param = new <span class="hljs-type">GoodsParam</span>();
    param.setGoods_name(<span class="hljs-string">"iphone6"</span>);
    <span class="hljs-type">GoodsReq</span> req = new <span class="hljs-type">GoodsReq</span>(<span class="hljs-string">"file.upload"</span>, param);

    <span class="hljs-type">String</span> path = this.getClass().getResource(<span class="hljs-string">""</span>).getPath();

    <span class="hljs-type">List</span>&lt;<span class="hljs-type">UploadFile</span>&gt; files = new <span class="hljs-type">ArrayList</span>&lt;&gt;();

    // 这里的headImg，idcardImg要跟服务端参数名对应
    files.add(new <span class="hljs-type">UploadFile</span>(<span class="hljs-string">"headImg"</span>, new <span class="hljs-type">File</span>(path + <span class="hljs-string">"1.txt"</span>)));
    files.add(new <span class="hljs-type">UploadFile</span>(<span class="hljs-string">"idcardImg"</span>, new <span class="hljs-type">File</span>(path + <span class="hljs-string">"2.txt"</span>)));

    <span class="hljs-type">GoodsResp</span> <span class="hljs-literal">result</span> = client.requestFile(req, files);

    <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"--------------------"</span>);
    <span class="hljs-keyword">if</span> (<span class="hljs-literal">result</span>.isSuccess()) {
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-literal">result</span>.getData());
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"errorMsg:"</span> + <span class="hljs-literal">result</span>.getMsg());
    }
    <span class="hljs-type">System</span>.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"--------------------"</span>);
}
</code></pre>
                        <ul>
                            <li>服务端处理</li>
                        </ul>
                        <pre><code class="hljs">@Api(name = <span class="hljs-string">"file.upload"</span>)
    @ApiDocMethod(<span class="hljs-keyword">description</span> = <span class="hljs-string">"文件上传"</span>)
    Object upload(UploadParam param) <span class="hljs-keyword">throws</span> IllegalStateException, IOException {
        <span class="hljs-comment">// 获取上传文件</span>
        MultipartFile headImgFile = param.getHeadImg();
        MultipartFile idcardImgFile = param.getIdcardImg();

        StringBuilder sb = <span class="hljs-keyword">new</span> StringBuilder();
        sb.<span class="hljs-keyword">append</span>(<span class="hljs-string">"表单名："</span>).<span class="hljs-keyword">append</span>(headImgFile.getName()).<span class="hljs-keyword">append</span>(<span class="hljs-string">","</span>)
        .<span class="hljs-keyword">append</span>(<span class="hljs-string">"文件大小："</span>).<span class="hljs-keyword">append</span>(headImgFile.getSize()).<span class="hljs-keyword">append</span>(<span class="hljs-string">";"</span>);

        sb.<span class="hljs-keyword">append</span>(<span class="hljs-string">"表单名："</span>).<span class="hljs-keyword">append</span>(idcardImgFile.getName()).<span class="hljs-keyword">append</span>(<span class="hljs-string">","</span>)
        .<span class="hljs-keyword">append</span>(<span class="hljs-string">"文件大小："</span>).<span class="hljs-keyword">append</span>(idcardImgFile.getSize()).<span class="hljs-keyword">append</span>(<span class="hljs-string">";"</span>);

        <span class="hljs-comment">// headImgFile.getInputStream(); // 返回文件流</span>
        <span class="hljs-comment">// headImgFile.getBytes(); // 返回文件数据流</span>

        headImgFile.transferTo(<span class="hljs-keyword">new</span> <span class="hljs-keyword">File</span>(<span class="hljs-string">"D:/new_"</span> + headImgFile.getOriginalFilename()));
        idcardImgFile.transferTo(<span class="hljs-keyword">new</span> <span class="hljs-keyword">File</span>(<span class="hljs-string">"D:/new_"</span> + idcardImgFile.getOriginalFilename()));

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ApiResult(sb.toString());
    }
</code></pre>
                        <ul>
                            <li>UploadParam.java</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-tag">public</span> <span class="hljs-tag">class</span> <span class="hljs-tag">UploadParam</span> {

    <span class="hljs-variable">@ApiDocField</span>(description = <span class="hljs-string">"商品名称"</span>, required = true, example = <span class="hljs-string">"iphoneX"</span>)
    <span class="hljs-variable">@NotEmpty</span>(message = <span class="hljs-string">"商品名称不能为空"</span>)
    <span class="hljs-variable">@Length</span>(min = <span class="hljs-number">3</span>, max = <span class="hljs-number">20</span>, message = <span class="hljs-string">"{goods.name.length}=3,20"</span>)
    private String goods_name;

    <span class="hljs-comment">// 这里定义上传的文件，属性名称对应客户端上传的name</span>
    <span class="hljs-variable">@ApiDocField</span>(description = <span class="hljs-string">"头像图片"</span>, required = true, dataType = DataType.FILE)
    <span class="hljs-variable">@NotNull</span>(message = <span class="hljs-string">"请上传头像图片"</span>)
    private MultipartFile headImg;

    <span class="hljs-variable">@ApiDocField</span>(description = <span class="hljs-string">"身份证图片"</span>, required = true, dataType = DataType.FILE)
    <span class="hljs-variable">@NotNull</span>(message = <span class="hljs-string">"请上传身份证图片"</span>)
    private MultipartFile idcardImg;

    <span class="hljs-comment">//getter,setter</span>

}
</code></pre>
                        <p><code>headImg</code>,<code>idcardImg</code>就是上传的表单名，客户端需要于此对应。</p>
                        <h2>上传内存文件</h2>
                        <p>有些文件不是从本地读取的，而是从其它地方下载到内存中，比如从阿里云下载文件到内存中，不落地。</p>
                        <pre><code class="hljs"><span class="hljs-type">List</span>&lt;<span class="hljs-type">UploadFile</span>&gt; files = new <span class="hljs-type">ArrayList</span>&lt;&gt;();

files.add(new <span class="hljs-type">UploadFile</span>(<span class="hljs-string">"headImg"</span>,<span class="hljs-string">"headImg.txt"</span>, this.getClass().getResourceAsStream(<span class="hljs-string">"1.txt"</span>)));
files.add(new <span class="hljs-type">UploadFile</span>(<span class="hljs-string">"idcardImg"</span>, <span class="hljs-string">"idcardImg.txt"</span>, this.getClass().getResourceAsStream(<span class="hljs-string">"2.txt"</span>)));

<span class="hljs-type">GoodsResp</span> <span class="hljs-literal">result</span> = client.requestFile(req, files);
</code></pre>
                        <p>或者</p>
                        <pre><code class="hljs"><span class="hljs-type">List</span>&lt;<span class="hljs-type">UploadFile</span>&gt; files = new <span class="hljs-type">ArrayList</span>&lt;&gt;();

files.add(new <span class="hljs-type">UploadFile</span>(<span class="hljs-string">"headImg"</span>,<span class="hljs-string">"headImg.txt"</span>, byte[]));
files.add(new <span class="hljs-type">UploadFile</span>(<span class="hljs-string">"idcardImg"</span>, <span class="hljs-string">"idcardImg.txt"</span>, byte[]));

<span class="hljs-type">GoodsResp</span> <span class="hljs-literal">result</span> = client.requestFile(req, files);
</code></pre>
                        <p>主要通过UploadFile类的构造方法来区分</p>
                        <pre><code class="hljs">/<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@param name 表单名称，不能重复</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@param file 文件</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@throws IOException</span>
 <span class="hljs-keyword">*</span>/
public UploadFile(String name, File file)

/<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@param name 表单名称，不能重复</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@param fileName 文件名</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@param input 文件流</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@throws IOException</span>
 <span class="hljs-keyword">*</span>/
public UploadFile(String name, String fileName, InputStream input)

/<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@param name 表单名称，不能重复</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@param fileName 文件名</span>
 <span class="hljs-keyword">*</span> <span class="hljs-comment">@param fileData 文件数据</span>
 <span class="hljs-keyword">*</span>/
public UploadFile(String name, String fileName, byte[] fileData)
</code></pre>
                        <h1>限流功能（v1.9.1）</h1>
                        <p>针对每个接口做限流功能，限流方式有两种：</p>
                        <ul>
                            <li>漏桶策略：每秒处理固定数量的请求，超出请求返回错误信息。可用在秒杀、抢购业务</li>
                            <li>令牌桶策略：每秒放置固定数量的令牌数，不足的令牌数做等待处理，直到拿到令牌为止。平滑输出，可减轻服务器压力。</li>
                        </ul>
                        <p>两种策略可在后台页面切换</p>
                        <h2>开启限流功能</h2>
                        <p>以springboot为例</p>
                        <ul>
                            <li>application.properties配置redis信息</li>
                            <li>IndexController中配置：</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-comment">@Autowired</span>
private RedisTemplate redisTemplate;

<span class="hljs-comment">@Override</span>
protected void initApiConfig(ApiConfig apiConfig) {
    ...

    // 配置拦截器
    apiConfig.setInterceptors(
            new ApiInterceptor[] {
                    new LimitInterceptor() // 限流拦截器,放在首位
                    ...
                    });


    /<span class="hljs-keyword">*</span>
     <span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span>设置限流管理<span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span><span class="hljs-keyword">*</span> <span class="hljs-keyword">*</span>/
    apiConfig.setLimitManager(new ApiLimitManager(redisTemplate));
    // [可选]，设置配置页面访问密码
    //apiConfig.setLimitPassword(limitPassword);
    // [可选]，设置【策略】，默认为限流策略
    //apiConfig.setDefaultLimitType(defaultLimitType);
    // [可选]，设置【每秒可处理请求数】，默认50。策略为限流策略时，该值生效
    //apiConfig.setDefaultLimitCount(defaultLimitCount);
    // [可选]，设置【令牌桶容量】，默认50。策略为令牌桶策略时，该值生效
    //apiConfig.setDefaultTokenBucketCount(defaultTokenBucketCount);
}
</code></pre>
                        <ul>
                            <li>访问：<a href="http://localhost:8080/api/limit">http://localhost:8080/api/limit</a>  对接口进行限流设置，默认密码limit123</li>
                        </ul>
                        <h1>文档页头部显示项目描述（v1.9.1）</h1>
                        <p>IndexController重写getDocRemark()方法</p>
                        <pre><code class="hljs"><span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">protected</span> <span class="hljs-function">String <span class="hljs-title">getDocRemark</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">"文档描述，支持html标签&lt;br&gt;"</span>;
}

</code></pre>
                        <h1>使用GET方式请求（v1.9.1）</h1>
                        <p>直接在url跟参数</p>
                        <p>如接口URL为：<a href="http://localhost:8080/api">http://localhost:8080/api</a></p>
                        <p>则完整地址为：</p>
                        <pre><code class="hljs">http:<span class="hljs-regexp">//localhost</span>:<span class="hljs-number">8080</span>/api?name=doc.result.<span class="hljs-number">5</span>&amp;version=&amp;app_key=test&amp;data=<span class="hljs-variable">%257B</span><span class="hljs-variable">%2522goods_name</span><span class="hljs-variable">%2522</span><span class="hljs-variable">%253A</span><span class="hljs-variable">%2522iphoneX</span><span class="hljs-variable">%2522</span><span class="hljs-variable">%257D</span>&amp;timestamp=<span class="hljs-number">2018</span>-<span class="hljs-number">06</span>-<span class="hljs-number">22</span><span class="hljs-variable">%2009</span><span class="hljs-variable">%3A38</span><span class="hljs-variable">%3A32</span>&amp;<span class="hljs-keyword">format</span>=json&amp;sign=<span class="hljs-number">552</span>BB4285F59C4CC230164E8E3BF4348
</code></pre>
                        <ul>
                            <li>注：参数值需要urlencode一下，如果用到上传功能，还是需要post方式</li>
                        </ul>
                        <h1>使用配置中心（v1.10.0）</h1>
                        <p>配置中心提供的功能</p>
                        <ul>
                            <li>接入方管理：统一管理appKey、secret、公私钥</li>
                            <li>权限管理：对某些接入方开放部分接口</li>
                            <li>限流管理：配置接口限流功能（如秒杀场景可用到）</li>
                        </ul>
                        <p>配置中心与接口应用分离，它们的交互方式采用Netty长连接保持通讯，并支持断开重连。交互图：</p>
                        <p><img src="https://images.gitee.com/uploads/images/2018/0728/094853_5bc4169d_332975.png" alt="配置中心交互图" title="QQ截图20180728094824.png"></p>
                        <ul>
                            <li>接口服务器启动之后会自动连接到配置中心</li>
                            <li>配置中心修改配置会推送给接口服务器，接口服务器会立马生效，这里采用长连接方式</li>
                        </ul>
                        <h2>配置中心使用方法</h2>
                        <h3>启动服务端（easyopen-config）</h3>
                        <ul>
                            <li>导入apiconf-demo.sql脚本</li>
                            <li>配置application.properties</li>
                            <li>启动后访问：<a href="http://localhost:8070/">http://localhost:8070/</a></li>
                            <li>用户名密码:admin 123456</li>
                        </ul>
                        <p><strong>注意：</strong> 建议先启动配置中心，再启动server接口应用。如果server先启动，发现配置中心没启动，会尝试加载本地配置，如果本地没有配置就启动失败。必须要正确连上配置中心一次后，下载配置文件到本地，之后启动顺序可以随意。</p>
                        <h3>启动前端页面（vue项目）</h3>
                        <ul>
                            <li>前提：先安装好npm，<a href="https://www.cnblogs.com/ghfjj/p/6364937.html">npm安装教程</a>。建议使用淘宝镜像。</li>
                            <li>打开src/utils/ApiUtil.js，修改url</li>
                        </ul>
                        <p>然后打开命令行窗口，输入下面命令</p>
                        <pre><code class="hljs"><span class="hljs-comment"># 初始化</span>
cnpm install

<span class="hljs-comment"># 启动，随后出现一个url，直接复制到浏览器即可</span>
cnpm run dev
</code></pre>
                        <h2>接口应用项目配置（easyopen-server-manual）</h2>
                        <p>配置中心启动完毕后，需要有接口应用连接过来。</p>
                        <ul>
                            <li>注意：这里的配置说明是接口应用，即demo下面的应用， <strong>不是</strong> 配置中心</li>
                        </ul>
                        <p>接口应用使用配置中心步骤如下：</p>
                        <p>打开<code>IndexController.java</code></p>
                        <ul>
                            <li>去掉默认的appKey，Secret配置</li>
                        </ul>
                        <p>去掉下面这个配置</p>
                        <p><s>Map&lt;String, String&gt; appSecretStore = new HashMap&lt;String, String&gt;();</s>
                            <s>appSecretStore.put(“test”, “123456”);</s>
                            <s>apiConfig.addAppSecret(appSecretStore);</s></p>
                        <ul>
                            <li>去掉已有的限流配置</li>
                        </ul>
                        <p><s>apiConfig.setLimitManager(new ApiLimitManager(redisTemplate));</s></p>
                        <ul>
                            <li>添加下面配置代码</li>
                        </ul>
                        <pre><code class="hljs"><span class="hljs-comment">// 配置拦截器</span>
apiConfig.setInterceptors(<span class="hljs-keyword">new</span> ApiInterceptor[] {
        <span class="hljs-comment">// 限流拦截器（配置中心）</span>
        <span class="hljs-keyword">new</span> LimitInterceptor() ,
        <span class="hljs-comment">// 权限拦截器（配置中心）</span>
        <span class="hljs-keyword">new</span> PermissionInterceptor()

        ... 其它拦截器
});

<span class="hljs-comment">/* -----------------配置中心------------------ */</span>
<span class="hljs-comment">/*
 *  appName 应用名称，应用名称，不能重复
 *  host    配置中心ip，config服务器ip
 *  port    配置中心端口，对应config中的netty.server.port
 */</span>
ConfigClient configClient = <span class="hljs-keyword">new</span> ConfigClient(<span class="hljs-string">"app1"</span>, <span class="hljs-string">"localhost"</span>, <span class="hljs-number">8071</span>);
<span class="hljs-comment">/*
如果要使用分布式业务限流，使用下面这句。默认是单机限流
configClient.setLimitManager(new ApiLimitManager(redisTemplate, new ApiLimitConfigLocalManager()));
*/</span>
apiConfig.setConfigClient(configClient);
<span class="hljs-comment">/* -------------------------------------- */</span>
</code></pre>
                        <p>配置完先启动配置中心，在启动server程序</p>
                        <h1>接口方法单值参数(v1.11.1)</h1>
                        <p>在此之前，接口方法参数必须是一个Object对象，如下面的GoodsParam：</p>
                        <pre><code class="hljs"><span class="hljs-annotation">@Api</span>(name = <span class="hljs-string">"param.type.1"</span>)
    <span class="hljs-annotation">@ApiDocMethod</span>(description = <span class="hljs-string">"参数类型，自定义类"</span>)
    <span class="hljs-keyword">public</span> <span class="hljs-function">String <span class="hljs-title">demo1</span><span class="hljs-params">(GoodsParam param)</span> </span>{
        <span class="hljs-function"><span class="hljs-keyword">return</span> JSON.<span class="hljs-title">toJSONString</span><span class="hljs-params">(param)</span></span>;
    }
</code></pre>
                        <p>有时候参数类里面只有一个属性，也要建一个类，这样做太麻烦，现在可以这样做：</p>
                        <pre><code class="hljs"><span class="hljs-annotation">@Api</span>(name = <span class="hljs-string">"param.type.4"</span>)
    <span class="hljs-annotation">@ApiDocMethod</span>(description = <span class="hljs-string">"参数类型，String接收"</span>)
    <span class="hljs-annotation">@ApiDocReturn</span>(description = <span class="hljs-string">"字符串返回值"</span>) <span class="hljs-comment">// 当返回值是基本类型或string时</span>
    public <span class="hljs-built_in">String</span> demo3(
            <span class="hljs-annotation">@NotBlank</span>(message = <span class="hljs-string">"不能为空"</span>)
            <span class="hljs-annotation">@Length</span>(min = <span class="hljs-number">3</span>, max = <span class="hljs-number">6</span>, message = <span class="hljs-string">"长度3-6"</span>)
            <span class="hljs-annotation">@ApiDocField</span>(description = <span class="hljs-string">"参数"</span>)
                    <span class="hljs-built_in">String</span> param) { <span class="hljs-comment">// 可直接申明参数，只能有一个参数，并且类型是基本类型或string</span>
        <span class="hljs-keyword">return</span> param;
    }
</code></pre>
                        <p>这样的话就不需要再新建类了。</p>
                        <h1>文件下载（v1.11.1）</h1>
                        <pre><code class="hljs"><span class="hljs-comment">// 下载文件，可用Postman请求</span>
    /*
        postman设置：
        POST URL:http:<span class="hljs-comment">//localhost:8080/api</span>
        body raw application/json
        {
            <span class="hljs-string">"name"</span>: <span class="hljs-string">"download.test"</span>,
            <span class="hljs-string">"version"</span>: <span class="hljs-string">""</span>,
            <span class="hljs-string">"data"</span>: <span class="hljs-string">"%7B%22goods_name%22%3A%22iphoneX%22%7D"</span>
        }

        点击sand <span class="hljs-keyword">and</span> download
      */
    @Api(name = <span class="hljs-string">"download.test"</span>, ignoreValidate = <span class="hljs-keyword">true</span>, noReturn = <span class="hljs-keyword">true</span>)
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> download(GoodsParam param) throws IOException {
        <span class="hljs-comment">// 获取response</span>
        HttpServletResponse response = ApiContext.getResponse();
        String fileName = <span class="hljs-string">"文件.txt"</span>;
        response.setHeader(<span class="hljs-string">"Content-Disposition"</span>, <span class="hljs-string">"attachment;filename="</span>+ URLEncoder.encode(fileName, <span class="hljs-string">"UTF-8"</span>));

        <span class="hljs-comment">// 下载application.properties文件</span>
        ClassPathResource resource = <span class="hljs-keyword">new</span> ClassPathResource(<span class="hljs-string">"application.properties"</span>);
        InputStream inputStream = resource.getInputStream();
        OutputStream outputStream = response.getOutputStream();
        IOUtils.copy(inputStream, outputStream);
    }
</code></pre>
                        <p>注意设置<code>noReturn</code>属性为true，表示不需要返回。</p>
                        <h1>mock请求(v1.12.0)</h1>
                        <p>前端人员马上要接口，后端功能都还没开始做的情况下，可以先定义好接口参数和返回结果，前端mock请求，返回假数据。mock数据根据文档注解生成，指定example属性即可</p>
                        <ul>
                            <li>mock请求格式：接口url/mock。接口url为http://localhost:8080/api，那么mock请求的url为http://localhost:8080/api/mock</li>
                        </ul>
                        <hr>
                        <h1>客户端请求代码</h1>
                        <h2>Java</h2>
                        <pre><code class="hljs"><span class="hljs-keyword">import</span> java.io.IOException;
<span class="hljs-keyword">import</span> java.net.URLEncoder;
<span class="hljs-keyword">import</span> java.security.MessageDigest;
<span class="hljs-keyword">import</span> java.<span class="hljs-built_in">text</span>.SimpleDateFormat;
<span class="hljs-keyword">import</span> java.util.ArrayList;
<span class="hljs-keyword">import</span> java.util.Collections;
<span class="hljs-keyword">import</span> java.util.Date;
<span class="hljs-keyword">import</span> java.util.<span class="hljs-keyword">HashMap</span>;
<span class="hljs-keyword">import</span> java.util.List;
<span class="hljs-keyword">import</span> java.util.Map;
<span class="hljs-keyword">import</span> java.util.Set;

<span class="hljs-keyword">import</span> org.junit.Test;

<span class="hljs-keyword">import</span> com.alibaba.fastjson.JSON;

<span class="hljs-keyword">import</span> junit.framework.TestCase;

<span class="hljs-keyword">public</span> class PostTest extends TestCase {

    @Test
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> testPost() <span class="hljs-keyword">throws</span> IOException {
        <span class="hljs-keyword">String</span> appKey = <span class="hljs-string">"test"</span>;
        <span class="hljs-keyword">String</span> secret = <span class="hljs-string">"123456"</span>;
        <span class="hljs-comment">// 业务参数</span>
        Map&lt;<span class="hljs-keyword">String</span>, <span class="hljs-keyword">String</span>&gt; jsonMap = <span class="hljs-keyword">new</span> <span class="hljs-keyword">HashMap</span>&lt;<span class="hljs-keyword">String</span>, <span class="hljs-keyword">String</span>&gt;();
        jsonMap.put(<span class="hljs-string">"goodsName"</span>, <span class="hljs-string">"iphoneX"</span>);

        <span class="hljs-keyword">String</span> json = JSON.toJSONString(jsonMap);
        json = URLEncoder.encode(json, <span class="hljs-string">"utf-8"</span>);

        <span class="hljs-comment">// 系统参数</span>
        Map&lt;<span class="hljs-keyword">String</span>, <span class="hljs-keyword">Object</span>&gt; param = <span class="hljs-keyword">new</span> <span class="hljs-keyword">HashMap</span>&lt;<span class="hljs-keyword">String</span>, <span class="hljs-keyword">Object</span>&gt;();
        param.put(<span class="hljs-string">"name"</span>, <span class="hljs-string">"goods.get"</span>);
        param.put(<span class="hljs-string">"app_key"</span>, appKey);
        param.put(<span class="hljs-string">"data"</span>, json);
        param.put(<span class="hljs-string">"timestamp"</span>, getTime());
        param.put(<span class="hljs-string">"version"</span>, <span class="hljs-string">""</span>);

        <span class="hljs-keyword">String</span> sign = buildSign(param, secret);

        param.put(<span class="hljs-string">"sign"</span>, sign);

        System.out.<span class="hljs-built_in">println</span>(<span class="hljs-string">"=====请求数据====="</span>);
        System.out.<span class="hljs-built_in">println</span>(JSON.toJSON(param));
    }

    <span class="hljs-comment">/**
     * 构建签名
     *
     * @param paramsMap
     *            参数
     * @param secret
     *            密钥
     * @return
     * @throws IOException
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">String</span> buildSign(Map&lt;<span class="hljs-keyword">String</span>, ?&gt; paramsMap, <span class="hljs-keyword">String</span> secret) <span class="hljs-keyword">throws</span> IOException {
        Set&lt;<span class="hljs-keyword">String</span>&gt; keySet = paramsMap.keySet();
        List&lt;<span class="hljs-keyword">String</span>&gt; paramNames = <span class="hljs-keyword">new</span> ArrayList&lt;<span class="hljs-keyword">String</span>&gt;(keySet);

        Collections.<span class="hljs-built_in">sort</span>(paramNames);

        StringBuilder paramNameValue = <span class="hljs-keyword">new</span> StringBuilder();

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">String</span> paramName : paramNames) {
            paramNameValue.<span class="hljs-built_in">append</span>(paramName).<span class="hljs-built_in">append</span>(paramsMap.<span class="hljs-built_in">get</span>(paramName));
        }

        <span class="hljs-keyword">String</span> source = secret + paramNameValue.toString() + secret;

        <span class="hljs-keyword">return</span> md5(source);
    }

    <span class="hljs-comment">/**
     * 生成md5,全部大写
     *
     * @param message
     * @return
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">String</span> md5(<span class="hljs-keyword">String</span> message) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// 1 创建一个提供信息摘要算法的对象，初始化为md5算法对象</span>
            MessageDigest md = MessageDigest.getInstance(<span class="hljs-string">"MD5"</span>);

            <span class="hljs-comment">// 2 将消息变成byte数组</span>
            <span class="hljs-built_in">byte</span>[] input = message.getBytes();

            <span class="hljs-comment">// 3 计算后获得字节数组,这就是那128位了</span>
            <span class="hljs-built_in">byte</span>[] buff = md.digest(input);

            <span class="hljs-comment">// 4 把数组每一字节（一个字节占八位）换成16进制连成md5字符串</span>
            <span class="hljs-keyword">return</span> byte2hex(buff);
        } <span class="hljs-keyword">catch</span> (Exception e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(e);
        }
    }

    <span class="hljs-comment">/**
     * 二进制转十六进制字符串
     *
     * @param bytes
     * @return
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">String</span> byte2hex(<span class="hljs-built_in">byte</span>[] bytes) {
        StringBuilder sign = <span class="hljs-keyword">new</span> StringBuilder();
        <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; bytes.length; i++) {
            <span class="hljs-keyword">String</span> <span class="hljs-built_in">hex</span> = Integer.toHexString(bytes[i] &amp; <span class="hljs-number">0xFF</span>);
            <span class="hljs-keyword">if</span> (<span class="hljs-built_in">hex</span>.length() == <span class="hljs-number">1</span>) {
                sign.<span class="hljs-built_in">append</span>(<span class="hljs-string">"0"</span>);
            }
            sign.<span class="hljs-built_in">append</span>(<span class="hljs-built_in">hex</span>.toUpperCase());
        }
        <span class="hljs-keyword">return</span> sign.toString();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">String</span> getTime() {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> SimpleDateFormat(<span class="hljs-string">"yyyy-MM-dd HH:mm:ss"</span>).format(<span class="hljs-keyword">new</span> Date());
    }
}

</code></pre>
                        <h2>JavaScript</h2>
                        <pre><code class="hljs"><span class="hljs-comment">/**
//需要发布到服务器上运行，并且server端需要处理跨域
//在IndexController.java上加@CrossOrigin(origins={"*"})

sdk.config({
    url : 'http://localhost:8080/api'
    ,app_key : 'test'
    ,secret : '123456'
    ,jwt : 'xxx'
});

sdk.post({
	name   : 'goods.get' // 接口名
// 	,version:'1.0'
// 	,access_token:''
	,data  : {'goods_name':'iphone'} // 请求参数
	,callback:function(resp) { // 成功回调
		console.log(resp)
	}
});

sdk.post({
	name   : 'goods.get' // 接口名
	,data  : {'goods_name':'iphone'} // 请求参数
	,callback:function(resp) { // 成功回调
		console.log(resp)
	}
});
 */</span>
;(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{

	<span class="hljs-keyword">var</span> config = {
		url : <span class="hljs-string">'http://127.0.0.1:8080/api'</span>
		,app_key : <span class="hljs-string">'test'</span>
		,secret : <span class="hljs-string">'123456'</span>
		,default_version : <span class="hljs-string">''</span>

	    ,api_name : <span class="hljs-string">"name"</span>
	    ,version_name : <span class="hljs-string">"version"</span>
	    ,app_key_name : <span class="hljs-string">"app_key"</span>
	    ,data_name : <span class="hljs-string">"data"</span>
	    ,timestamp_name : <span class="hljs-string">"timestamp"</span>
	    ,sign_name : <span class="hljs-string">"sign"</span>
	    ,format_name : <span class="hljs-string">"format"</span>
	    ,access_token_name : <span class="hljs-string">"access_token"</span>
	    ,jwt : <span class="hljs-string">''</span>
	}

	<span class="hljs-keyword">var</span> DEFAULT_FORMAT = <span class="hljs-string">'json'</span>;
	<span class="hljs-keyword">var</span> UPLOAD_FORM_DATA_NAME = <span class="hljs-string">'body_data'</span>;
	<span class="hljs-keyword">var</span> CONTENT_TYPE_JSON = <span class="hljs-string">'application/json'</span>;

	<span class="hljs-keyword">var</span> CONTENT_TYPE_KEY = <span class="hljs-string">'Content-Type'</span>;
	<span class="hljs-keyword">var</span> POST_TYPE = <span class="hljs-string">'POST'</span>;
	<span class="hljs-keyword">var</span> GET_TYPE = <span class="hljs-string">'GET'</span>;

	<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">copy</span><span class="hljs-params">(source, target)</span> </span>{
        <span class="hljs-keyword">if</span> (target &amp;&amp; source &amp;&amp; typeof source == <span class="hljs-string">'object'</span>) {
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> p in source) {
            	target[p] = source[p];
            }
        }
        <span class="hljs-keyword">return</span> target;
    }

	<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add0</span><span class="hljs-params">(m)</span></span>{<span class="hljs-keyword">return</span> m&lt;<span class="hljs-number">10</span>?<span class="hljs-string">'0'</span>+m:m }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatDate</span><span class="hljs-params">(time)</span>
    </span>{
        <span class="hljs-keyword">var</span> y = time.getFullYear();
        <span class="hljs-keyword">var</span> m = time.getMonth()+<span class="hljs-number">1</span>;
        <span class="hljs-keyword">var</span> d = time.getDate();
        <span class="hljs-keyword">var</span> h = time.getHours();
        <span class="hljs-keyword">var</span> mm = time.getMinutes();
        <span class="hljs-keyword">var</span> s = time.getSeconds();
        <span class="hljs-keyword">return</span> y+<span class="hljs-string">'-'</span>+add0(m)+<span class="hljs-string">'-'</span>+add0(d)+<span class="hljs-string">' '</span>+add0(h)+<span class="hljs-string">':'</span>+add0(mm)+<span class="hljs-string">':'</span>+add0(s);
    }

    <span class="hljs-comment">/** 构建签名 */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">buildSign</span><span class="hljs-params">(postData,secret)</span> </span>{
        <span class="hljs-keyword">var</span> paramNames = [];
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">var</span> key in postData) {
            paramNames.push(key);
        }

        paramNames.sort();

        <span class="hljs-keyword">var</span> paramNameValue = [];

        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">var</span> i=<span class="hljs-number">0</span>,len=paramNames.length;i&lt;len;i++) {
            <span class="hljs-keyword">var</span> paramName = paramNames[i];
            paramNameValue.push(paramName);
            paramNameValue.push(postData[paramName]);
        }

        <span class="hljs-keyword">var</span> source = secret + paramNameValue.join(<span class="hljs-string">''</span>) + secret;
        <span class="hljs-comment">// MD5算法参见http://pajhome.org.uk/crypt/md5/</span>
        <span class="hljs-keyword">return</span> hex_md5(source).toUpperCase();
    }

	<span class="hljs-keyword">var</span> ajax = {
		<span class="hljs-comment">/**
		 * 提交请求
		 *<span class="hljs-phpdoc"> @param</span> opts.url 请求url
		 *<span class="hljs-phpdoc"> @param</span> opts.params 请求参数json
		 *<span class="hljs-phpdoc"> @param</span> opts.headers 请求header，json
		 *<span class="hljs-phpdoc"> @param</span> opts.form 表单dom对象
		 *<span class="hljs-phpdoc"> @param</span> opts.callback 回调
		 *<span class="hljs-phpdoc"> @param</span> opts.error 错误回调
		 */</span>
		request:<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(opts)</span> </span>{
			<span class="hljs-keyword">var</span> url = opts.url;
			<span class="hljs-keyword">var</span> params = opts.params;
			<span class="hljs-keyword">var</span> headers = opts.headers;
			<span class="hljs-keyword">var</span> form = opts.form;
			<span class="hljs-keyword">var</span> callback = opts.callback;
			<span class="hljs-keyword">var</span> error = opts.error || <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(e)</span></span>{alert(<span class="hljs-string">'数据请求失败'</span>)};
			<span class="hljs-keyword">var</span> method = opts.method;
			<span class="hljs-keyword">var</span> paramStr = JSON.stringify(params);

			<span class="hljs-keyword">var</span> xhr = this.createXhrObject();

			xhr.onreadystatechange = <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
				<span class="hljs-keyword">var</span> jsonData = <span class="hljs-string">''</span>;
				<span class="hljs-keyword">if</span> (xhr.readyState == <span class="hljs-number">4</span>){
					<span class="hljs-keyword">var</span> status = xhr.status;
		            <span class="hljs-keyword">if</span> ((status &gt;= <span class="hljs-number">200</span> &amp;&amp; status &lt; <span class="hljs-number">300</span>) || status == <span class="hljs-number">304</span>){
		            	jsonData = JSON.parse(xhr.responseText);
		            	callback(jsonData, paramStr);
		            } <span class="hljs-keyword">else</span> {
		            	jsonData = JSON.parse(<span class="hljs-string">'{"message":"后台请求错误(status:'</span> + status + <span class="hljs-string">')"}'</span>);
		            	console.log(xhr.responseText)
		            	error(jsonData, paramStr);
		            }
		        }
			};

			<span class="hljs-comment">// 添加header</span>
			<span class="hljs-keyword">if</span>(headers) {
				<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> key in headers) {
					xhr.setRequestHeader(key,headers[key]);
				}
			}

			<span class="hljs-keyword">if</span>(method == GET_TYPE) {
				<span class="hljs-keyword">var</span> queryString = [];
				<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> key in params) {
					<span class="hljs-keyword">var</span> p = key + <span class="hljs-string">'='</span> + encodeURIComponent(params[key]);
					queryString.push(p);
				}
				url = url + <span class="hljs-string">'?'</span> + queryString.join(<span class="hljs-string">'&amp;'</span>);
				xhr.open(method, url, <span class="hljs-keyword">true</span>);
				xhr.send();
			} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(method == POST_TYPE) {
				xhr.open(method, url, <span class="hljs-keyword">true</span>);
				<span class="hljs-keyword">if</span>(form) {
					<span class="hljs-keyword">var</span> formData = <span class="hljs-keyword">new</span> FormData(form);
					<span class="hljs-comment">// 添加json</span>
					formData.append(UPLOAD_FORM_DATA_NAME, paramStr);
					xhr.send(formData);
				} <span class="hljs-keyword">else</span> {
					xhr.setRequestHeader(CONTENT_TYPE_KEY, CONTENT_TYPE_JSON);
					xhr.send(paramStr);
				}
			} <span class="hljs-keyword">else</span> {
				<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Error(<span class="hljs-string">'不支持的method：'</span> + method);
			}

		}
		<span class="hljs-comment">/**
		 * 创建XHR对象
		 *<span class="hljs-phpdoc"> @private</span>
		 */</span>
		,createXhrObject:<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
			<span class="hljs-keyword">var</span> methods = [
				<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{ <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> XMLHttpRequest();}
				,<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{ <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ActiveXObject(<span class="hljs-string">'Msxml2.XMLHTTP'</span>);}
				,<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{ <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ActiveXObject(<span class="hljs-string">'Microsoft.XMLHTTP'</span>);}
			];

			<span class="hljs-keyword">var</span> xhr = <span class="hljs-keyword">null</span>;
			<span class="hljs-keyword">for</span>(<span class="hljs-keyword">var</span> i=<span class="hljs-number">0</span>,len=methods.length; i&lt;len; i++) {
				<span class="hljs-keyword">try</span> {
					xhr = methods[i]();
				} <span class="hljs-keyword">catch</span> (e) {
					<span class="hljs-keyword">continue</span>;
				}

				this.createXhrObject = methods[i];

				<span class="hljs-keyword">return</span> xhr;
			}

			<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Error(<span class="hljs-string">"创建XHR对象失败"</span>);
		}
	}

	<span class="hljs-comment">/**
	 * 请求
	 *<span class="hljs-phpdoc"> @param</span> opts.name 接口名
	 *<span class="hljs-phpdoc"> @param</span> opts.version 版本号
	 *<span class="hljs-phpdoc"> @param</span> opts.method 请求方式
	 *<span class="hljs-phpdoc"> @param</span> opts.data 请求数据，json对象
	 *<span class="hljs-phpdoc"> @param</span> opts.access_token
	 *<span class="hljs-phpdoc"> @param</span> opts.form 表单dom
	 *<span class="hljs-phpdoc"> @param</span> opts.callback 响应回调
	 *<span class="hljs-phpdoc"> @param</span> opts.jwt jwt
	 */</span>
	<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">request</span><span class="hljs-params">(opts)</span> </span>{
		<span class="hljs-keyword">var</span> name 		= opts.name,
		version 	= opts.version || config.default_version,
		data 		= opts.data || {},
		callback 	= opts.callback,
		accessToken = opts.access_token,
		url			= opts.url || config.url;

		<span class="hljs-keyword">var</span> headers = {};
		<span class="hljs-keyword">var</span> postData = {};

	    postData[config.api_name] = name;
	    postData[config.version_name] = version;
	    postData[config.app_key_name] = config.app_key;
	    postData[config.data_name] = encodeURIComponent(JSON.stringify(data));
	    postData[config.timestamp_name] = formatDate(<span class="hljs-keyword">new</span> Date());
	    postData[config.format_name] = DEFAULT_FORMAT;

	    <span class="hljs-keyword">if</span>(accessToken) {
	    	postData[config.access_token_name] = accessToken;
	    }

	    postData[config.sign_name] = buildSign(postData,config.secret);

	    <span class="hljs-keyword">var</span> jwt = opts.jwt || config.jwt; <span class="hljs-comment">// 优先使用opts中的jwt</span>
	    <span class="hljs-keyword">if</span>(jwt) {
	    	headers[<span class="hljs-string">'Authorization'</span>] = <span class="hljs-string">'Bearer '</span> + jwt;
	    }

		ajax.request({
			url:url
			,method:opts.method
			,params:postData
			,headers:headers
			,form:opts.form
			,callback:callback
            ,error:opts.error
		});
	}

	<span class="hljs-keyword">var</span> sdk = {
		config:<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(cfg)</span> </span>{
			copy(cfg,config);
		}
		<span class="hljs-comment">/**
		 * post请求
		 *<span class="hljs-phpdoc"> @param</span> opts.name 接口名
		 *<span class="hljs-phpdoc"> @param</span> opts.version 版本号
		 *<span class="hljs-phpdoc"> @param</span> opts.data 请求数据，json对象
		 *<span class="hljs-phpdoc"> @param</span> opts.access_token
		 *<span class="hljs-phpdoc"> @param</span> opts.form 表单dom
		 *<span class="hljs-phpdoc"> @param</span> opts.callback 响应回调
		 *<span class="hljs-phpdoc"> @param</span> opts.jwt jwt
		 */</span>
		,post:<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(opts)</span> </span>{
			opts.method = POST_TYPE;
			request(opts);
		}
		<span class="hljs-comment">/**
		 * get请求
		 *<span class="hljs-phpdoc"> @param</span> opts.name 接口名
		 *<span class="hljs-phpdoc"> @param</span> opts.version 版本号
		 *<span class="hljs-phpdoc"> @param</span> opts.data 请求数据，json对象
		 *<span class="hljs-phpdoc"> @param</span> opts.access_token
		 *<span class="hljs-phpdoc"> @param</span> opts.callback 响应回调
		 *<span class="hljs-phpdoc"> @param</span> opts.jwt jwt
		 */</span>
		,get:<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(opts)</span> </span>{
			<span class="hljs-keyword">if</span>(opts.form) {
				<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Error(<span class="hljs-string">'get方式不支持上传功能'</span>);
			}
			opts.method = GET_TYPE;
			request(opts);
 		}

	}

	window.sdk = sdk;

})();
</code></pre>
                        <h1>常见问题</h1>
                        <h2>服务请求(x)超时</h2>
                        <p>核对客户端时间跟服务器时间，误差不能超过3秒。调大误差变为5秒，使用<code>apiConfig.setTimeoutSeconds(5);</code></p>
                        <p>最好是做服务器时间同步</p>
                        <h2>在业务方法中如何获取request对象？</h2>
                        <p>ApiContext.getRequest()</p>
                        <h2>SDK加密传输json解析错误</h2>
                        <p>如果使用了shiro等权限框架，注意配置url，IndexController里面有多个url，除了api，还有api/doc,api/ssl等，这些url都需要允许访问
                            可以使用api*通配符来允许访问。</p>
                        <h2>如何获取当前调用者的appKey?</h2>
                        <p>ApiContext.getApiParam().fatchAppKey()</p>
                        <h2>直接返回方法中的结果</h2>
                        <pre><code class="hljs"><span class="hljs-annotation">@Api</span>(name = <span class="hljs-string">"xxx"</span>
            , wrapResult = <span class="hljs-keyword">false</span> <span class="hljs-comment">// 对结果不进行包装，直接将ApiResult转成json形式返回</span>
            )
    <span class="hljs-keyword">public</span> <span class="hljs-function">ApiResult <span class="hljs-title">fun</span><span class="hljs-params">(GoodsParam param)</span> </span>{
        ApiResult apiResult = <span class="hljs-keyword">new</span> ApiResult();
        apiResult.setCode(<span class="hljs-number">200</span>);
        apiResult.setMsg(<span class="hljs-string">"xxx"</span>);
        <span class="hljs-keyword">return</span> apiResult;
    }
</code></pre>
                        <h2>拦截器中使用spring注入</h2>
                        <p>// 第一步</p>
                        <pre><code class="hljs"><span class="hljs-annotation">@Component</span> <span class="hljs-comment">// 加这个注解</span>
public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyInterceptor</span> <span class="hljs-keyword"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">ApiInterceptorAdapter</span> {</span>
...
}
</code></pre>
                        <p>// 第二步</p>
                        <pre><code class="hljs">@Controller
@RequestMapping(<span class="hljs-string">"/api"</span>)
public class IndexController extends ApiController {

    @Autowired
    private MyInterceptor myInterceptor;

    @Override
    protected void initApiConfig(ApiConfig apiConfig) {
        <span class="hljs-keyword">...</span>
        apiConfig.setInterceptors(new ApiInterceptor[]{myInterceptor});
        <span class="hljs-keyword">...</span>
    }

}
</code></pre>
                        <h1>系统错误码</h1>
                        <p>具体参见：<code>com.gitee.easyopen.message.Errors.java</code></p>
                        <p>注：0~100为系统占用的code码</p>
                        <ul>
                            <li>-9=系统错误</li>
                            <li>1=调用不存在的服务请求</li>
                            <li>2=服务请求参数非法</li>
                            <li>3=服务请求缺少应用键参数</li>
                            <li>4=服务请求的应用键参数无效</li>
                            <li>5=服务请求需要签名,缺少签名参数</li>
                            <li>6=服务请求的签名无效</li>
                            <li>7=服务请求超时</li>
                            <li>8=服务请求业务逻辑出错</li>
                            <li>9=服务不可用</li>
                            <li>10=服务请求时间格式有误</li>
                            <li>11=服务请求序列化格式错误</li>
                            <li>12=服务请求出错， contentType 不支持</li>
                            <li>13=JSON 格式错误:</li>
                            <li>14=accessToken错误</li>
                            <li>15=accessToken已过期</li>
                            <li>16=未设置accessToken</li>
                            <li>17=操作token错误</li>
                            <li>18=token错误</li>
                            <li>19=算法不支持</li>
                            <li>20=ssl交互错误</li>
                            <li>21=jwt已过期</li>
                        </ul>
                    </div>
<!-- ***********************************************************内容分割线****************************************************************** -->
<h1>md5.js</h1>
<a  href="static/md5.js" target="_blank">md5.js</a>
                </article>
            </div>
        </div>
    <script src="./static/jquery-1.10.2.min.js"></script>
    <script src="./static/jquery.ztree.all-3.5.min.js"></script>
    <script src="./static/jquery.ztree_toc.min.js"></script>
    <script type="text/javascript">
    var title = document.title;
        $(document).ready(function(){
            $('#tree').ztree_toc({
            	_header_nodes: [{ id:1, pId:0, name:title,open:false}],  // 第一个节点
            	ztreeSetting: {
            		view: {
        				dblClickExpand: false,
        				showLine: true,
        				showIcon: false,
        				selectedMulti: false
        			},
        			data: {
        				simpleData: {
        					enable: true,
        					idKey : "id",
        					pIdKey: "pId"
        					// rootPId: "0"
        				}
        			},
            		callback: {
        				beforeClick: function(treeId, treeNode) {
        					$('a').removeClass('curSelectedNode');
        					if(treeNode.id == 1){
        						$('body').scrollTop(0);
        					}
        					if($.fn.ztree_toc.defaults.is_highlight_selected_line == true) {
        						$('#' + treeNode.id).css('color' ,'red').fadeOut("slow" ,function() {
        							$(this).show().css('color','black');
        						});
        					}
        				}
        			}            		
            	},
                is_auto_number:true, // 菜单是否显示编号，如果markdown标题没有数字标号可以设为true
                documment_selector:'.markdown-body',
                is_expand_all: true // 菜单全部展开
            });
            // 代码高亮
            $('.highlight').each(function(i, block) {
          	    hljs.highlightBlock(block);
          	});
        });
    </script>
    </body>
</html>